Skip to content
Back to skills

Phy Deserialization Audit

ASecurity

Unsafe deserialization vulnerability scanner (OWASP A08:2021). Detects Python pickle/yaml/eval, Java ObjectInputStream/XStream/XMLDecoder, PHP unserialize, Ruby Marshal.load, Node.js eval/new Function/vm, Go gob with interface{}. Traces HTTP input sources to dangerous sinks, classifies CRITICAL/HIGH/MEDIUM, outputs CWE/CVE mappings and per-language fix snippets. Zero competitors on ClawHub.

  • 2,128 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added June 6, 2026
developmentjavascripttypescriptpythonrustgojavarubyphpbashnode

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned June 6, 2026

npx -y skills add LeoYeAI/openclaw-master-skills --skill phy-deserialization-audit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Phy Deserialization Audit?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Phy Deserialization Audit
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/leoyeai-phy-deserialization-audit/badge)](https://www.skillsdirectory.com/skills/leoyeai-phy-deserialization-audit)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: phy-deserialization-audit
description: Unsafe deserialization vulnerability scanner (OWASP A08:2021). Detects Python pickle/yaml/eval, Java ObjectInputStream/XStream/XMLDecoder, PHP unserialize, Ruby Marshal.load, Node.js eval/new Function/vm, Go gob with interface{}. Traces HTTP input sources to dangerous sinks, classifies CRITICAL/HIGH/MEDIUM, outputs CWE/CVE mappings and per-language fix snippets. Zero competitors on ClawHub.
license: Apache-2.0
tags:
  - security
  - deserialization
  - owasp
  - python
  - java
  - php
  - ruby
  - nodejs
  - vulnerability-scanner
metadata:
  author: PHY041
  version: "1.0.0"
---

# phy-deserialization-audit

Static scanner for **OWASP A08:2021 — Insecure Deserialization** vulnerabilities across Python, Java, PHP, Ruby, Node.js/TypeScript, and Go codebases. No API keys, no network calls, no dependencies beyond Python 3 stdlib.

## What It Detects

### Python
| Pattern | Severity | CVE/CWE |
|---------|----------|---------|
| `pickle.loads(user_data)` | CRITICAL | CWE-502 |
| `pickle.load(untrusted_file)` | CRITICAL | CWE-502 |
| `yaml.load(data)` without SafeLoader | HIGH | CVE-2017-18342 |
| `yaml.full_load()` / `yaml.unsafe_load()` | CRITICAL | CVE-2017-18342 |
| `jsonpickle.decode(input)` | CRITICAL | CWE-502 |
| `marshal.loads(data)` | HIGH | CWE-502 |
| `eval(user_input)` / `exec(user_input)` | CRITICAL | CWE-95 |
| `shelve.open(user_controlled_path)` | HIGH | CWE-502 |

### Java
| Pattern | Severity | CVE/CWE |
|---------|----------|---------|
| `new ObjectInputStream(...).readObject()` | CRITICAL | CWE-502, gadget chains |
| `XStream.fromXML(userInput)` | CRITICAL | CVE-2021-29505 |
| `new XMLDecoder(inputStream)` | CRITICAL | CWE-502 |
| `ObjectMapper.readValue(input, Object.class)` | HIGH | CVE-2017-7525 (Jackson polymorphic) |
| `Serializable` class with `readObject()` override | HIGH | CWE-502 |
| `new ObjectMapper().enableDefaultTyping()` | HIGH | CVE-2017-7525 |

### PHP
| Pattern | Severity | CVE/CWE |
|---------|----------|---------|
| `unserialize($userInput)` | CRITICAL | CWE-502, POP chains |
| `unserialize($_GET[...])` / `unserialize($_POST[...])` | CRITICAL | CWE-502 |
| `unserialize($_COOKIE[...])` | CRITICAL | CWE-502 |
| `unserialize(base64_decode(...))` | HIGH | CWE-502 |

### Ruby
| Pattern | Severity | CVE/CWE |
|---------|----------|---------|
| `Marshal.load(user_input)` | CRITICAL | CWE-502 |
| `YAML.load(user_input)` (Psych < 4.0 default) | HIGH | CVE-2013-4164 |
| `JSON.load(input)` (bypasses safe defaults) | MEDIUM | CWE-502 |

### Node.js / TypeScript
| Pattern | Severity | CVE/CWE |
|---------|----------|---------|
| `eval(req.body.*)` or `eval(req.params.*)` | CRITICAL | CWE-95 |
| `new Function(userInput)` | CRITICAL | CWE-95 |
| `vm.runInContext(userInput, ...)` | HIGH | CWE-94 |
| `vm.Script(userInput).runIn*` | HIGH | CWE-94 |
| `require(userControlledPath)` | HIGH | CWE-706 |
| `child_process.exec(unsanitizedInput)` | CRITICAL | CWE-78 (adjacent) |

### Go
| Pattern | Severity | CVE/CWE |
|---------|----------|---------|
| `gob.NewDecoder(conn).Decode(&interface{})` | HIGH | CWE-502 |
| `encoding/xml.Unmarshal` with `interface{}` target | MEDIUM | CWE-502 |
| `json.Unmarshal` into `interface{}` then unsafe cast | MEDIUM | CWE-20 |

## Taint Flow Logic

The scanner uses a two-pass approach:

**Pass 1 — Dangerous sink detection:** Find all pattern matches per file.

**Pass 2 — HTTP input proximity check:** Within the same function block (±40 lines), look for HTTP input markers:
- Python: `request.body`, `request.data`, `request.POST`, `request.GET`, `flask.request`, `request.json`
- Java: `HttpServletRequest`, `@RequestBody`, `@RequestParam`, `getParameter(`, `getInputStream(`
- PHP: `$_GET`, `$_POST`, `$_REQUEST`, `$_COOKIE`, `$_FILES`, `file_get_contents("php://input")`
- Ruby: `params[`, `request.body`, `JSON.parse(request.body)`
- Node.js: `req.body`, `req.params`, `req.query`, `req.headers`, `request.body`
- Go: `r.Body`, `r.URL.Query()`, `r.FormValue(`

If HTTP input marker found near sink → **CRITICAL** or **HIGH**
If no HTTP input marker visible → downgrade one level (informational) with note: *"Verify data source"*

**Safe patterns (excluded):**
- `yaml.safe_load(...)` — OK
- `yaml.load(data, Loader=yaml.SafeLoader)` — OK
- `pickle.loads(STATIC_BYTES)` where argument is a literal — OK
- `eval("1 + 2")` with string literal — OK

## Implementation

```python
#!/usr/bin/env python3
"""
phy-deserialization-audit — OWASP A08:2021 scanner
Usage: python3 audit_deserial.py [path] [--json] [--ci]
"""
import argparse
import json
import os
import re
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional

# ─── Severity ────────────────────────────────────────────────────────────────
CRITICAL, HIGH, MEDIUM, INFO = "CRITICAL", "HIGH", "MEDIUM", "INFO"

@dataclass
class Finding:
    file: str
    line: int
    pattern_name: str
    matched_text: str
    severity: str
    cwe: str
    cve: Optional[str]
    description: str
    fix: str
    has_http_taint: bool = False

# ─── Pattern registry ────────────────────────────────────────────────────────
# (pattern_name, regex, base_severity, cwe, cve, description, fix)
PATTERNS = {
    ".py": [
        ("PICKLE_LOADS",
         re.compile(r'\bpickle\.loads?\s*\('),
         CRITICAL, "CWE-502", None,
         "pickle.load/loads deserializes arbitrary Python objects — remote code execution if input is user-controlled.",
         "Never deserialize user input with pickle. Use json.loads() + schema validation (Pydantic/marshmallow)."),

        ("YAML_UNSAFE_LOAD",
         re.compile(r'\byaml\.(?:load|full_load|unsafe_load)\s*\((?![^)]*SafeLoader)'),
         HIGH, "CWE-502", "CVE-2017-18342",
         "yaml.load() without Loader=yaml.SafeLoader executes arbitrary Python code embedded in YAML.",
         "Replace with yaml.safe_load(data) or yaml.load(data, Loader=yaml.SafeLoader)."),

        ("JSONPICKLE_DECODE",
         re.compile(r'\bjsonpickle\.decode\s*\('),
         CRITICAL, "CWE-502", None,
         "jsonpickle.decode() restores full Python object graphs — arbitrary code execution.",
         "Do not use jsonpickle for untrusted input. Use json.loads() + strict schema."),

        ("MARSHAL_LOADS",
         re.compile(r'\bmarshal\.loads?\s*\('),
         HIGH, "CWE-502", None,
         "marshal is not intended for untrusted data and can execute code.",
         "Replace with json.loads() and validate schema."),

        ("EVAL_EXEC",
         re.compile(r'\b(?:eval|exec)\s*\('),
         CRITICAL, "CWE-95", None,
         "eval()/exec() with user-controlled input leads to arbitrary code execution.",
         "Remove eval/exec. Use ast.literal_eval() for safe literal evaluation, or a proper parser."),

        ("SHELVE_OPEN",
         re.compile(r'\bshelve\.open\s*\('),
         HIGH, "CWE-502", None,
         "shelve uses pickle internally — path traversal + deserialization risk.",
         "Ensure path is never user-controlled; prefer a proper database or JSON store."),
    ],
    ".java": [
        ("OBJECT_INPUT_STREAM",
         re.compile(r'\bnew\s+ObjectInputStream\s*\('),
         CRITICAL, "CWE-502", None,
         "Java deserialization via ObjectInputStream enables gadget-chain attacks (Apache Commons, Spring).",
         "Use a type-validating ObjectInputStream wrapper (e.g., Apache Commons ValidatingObjectInputStream) or replace with JSON/Protobuf."),

        ("XSTREAM_FROM_XML",
         re.compile(r'\.fromXML\s*\('),
         CRITICAL, "CWE-502", "CVE-2021-29505",
         "XStream.fromXML() can execute arbitrary code via crafted XML.",
         "Upgrade XStream ≥1.4.18 and enable allowlist: xstream.addPermission(NoTypePermission.NONE)."),

        ("XML_DECODER",
         re.compile(r'\bnew\s+XMLDecoder\s*\('),
         CRITICAL, "CWE-502", None,
         "XMLDecoder can instantiate arbitrary Java objects from XML — remote code execution.",
         "Never use XMLDecoder with untrusted input. Use a JSON parser or JAXB with an allowlist."),

        ("JACKSON_OBJECT_CLASS",
         re.compile(r'\.readValue\s*\([^,]+,\s*Object\.class\s*\)'),
         HIGH, "CWE-502", "CVE-2017-7525",
         "Jackson readValue to Object.class enables polymorphic deserialization attacks.",
         "Always specify a concrete class: mapper.readValue(json, MyDto.class)."),

        ("JACKSON_DEFAULT_TYPING",
         re.compile(r'\.enableDefaultTyping\s*\('),
         HIGH, "CWE-502", "CVE-2017-7525",
         "enableDefaultTyping() allows arbitrary class instantiation via @class field.",
         "Remove enableDefaultTyping(). Use @JsonTypeInfo on specific types only."),

        ("SERIALIZABLE_READOBJECT",
         re.compile(r'private\s+void\s+readObject\s*\(\s*ObjectInputStream'),
         HIGH, "CWE-502", None,
         "Custom readObject() override — verify it validates state before use.",
         "Validate all fields in readObject(); throw InvalidObjectException on unexpected values."),
    ],
    ".php": [
        ("PHP_UNSERIALIZE_INPUT",
         re.compile(r'\bunserialize\s*\(\s*\$(?:_GET|_POST|_REQUEST|_COOKIE|_SERVER)\b'),
         CRITICAL, "CWE-502", None,
         "unserialize() on HTTP input enables PHP Object Injection (POP chains → RCE).",
         "Never call unserialize() on user input. Use json_decode() with strict schema validation."),

        ("PHP_UNSERIALIZE_VAR",
         re.compile(r'\bunserialize\s*\('),
         HIGH, "CWE-502", None,
         "unserialize() on potentially user-controlled data.",
         "Verify data source. Replace with json_decode() wherever possible."),

        ("PHP_UNSERIALIZE_BASE64",
         re.compile(r'\bunserialize\s*\(\s*base64_decode\s*\('),
         CRITICAL, "CWE-502", None,
         "base64 obfuscation does not protect against PHP Object Injection.",
         "Remove entirely. Use signed tokens (JWT/HMAC) to pass state between requests."),
    ],
    ".rb": [
        ("RUBY_MARSHAL_LOAD",
         re.compile(r'\bMarshal\.load\s*\('),
         CRITICAL, "CWE-502", None,
         "Marshal.load executes arbitrary Ruby code from crafted binary data.",
         "Never deserialize untrusted data with Marshal. Use JSON.parse + schema validation."),

        ("RUBY_YAML_LOAD",
         re.compile(r'\bYAML\.load\s*\((?![^)]*safe)'),
         HIGH, "CWE-502", "CVE-2013-4164",
         "YAML.load() with Psych < 4.0 allows arbitrary object instantiation.",
         "Replace with YAML.safe_load() or YAML.load(data, permitted_classes: [])."),

        ("RUBY_JSON_LOAD",
         re.compile(r'\bJSON\.load\s*\('),
         MEDIUM, "CWE-502", None,
         "JSON.load() creates Ruby objects from JSON — prefer JSON.parse.",
         "Replace with JSON.parse() which only produces standard types."),
    ],
    ".js": _build_js_patterns(),
    ".ts": _build_js_patterns(),
    ".go": [
        ("GO_GOB_INTERFACE",
         re.compile(r'gob\.NewDecoder\s*\([^)]+\)\.Decode\s*\(&\s*(?:interface|any)\s*\{?\}?'),
         HIGH, "CWE-502", None,
         "gob decoding into interface{} without type registration is unsafe with untrusted data.",
         "Use gob.Register() to allowlist concrete types, or prefer encoding/json with a concrete struct."),

        ("GO_XML_INTERFACE",
         re.compile(r'xml\.Unmarshal\s*\([^,]+,\s*&\s*(?:interface|any)\s*\{?\}?'),
         MEDIUM, "CWE-502", None,
         "xml.Unmarshal into interface{} loses type safety.",
         "Unmarshal into a concrete struct type."),
    ],
}

def _build_js_patterns():
    return [
        ("JS_EVAL",
         re.compile(r'\beval\s*\('),
         CRITICAL, "CWE-95", None,
         "eval() with user-controlled input leads to arbitrary JS execution.",
         "Remove eval(). Use JSON.parse() for data, or a proper expression parser (e.g., mathjs)."),

        ("JS_NEW_FUNCTION",
         re.compile(r'\bnew\s+Function\s*\('),
         CRITICAL, "CWE-95", None,
         "new Function() constructs executable code — equivalent to eval().",
         "Avoid new Function() with user input. Use structured data and validation instead."),

        ("JS_VM_RUN",
         re.compile(r'\bvm\.(?:runIn(?:New)?Context|Script)\s*\('),
         HIGH, "CWE-94", None,
         "Node.js vm module does not provide a security sandbox — code can escape the context.",
         "Do not use vm for untrusted code execution. Use isolated-vm or a subprocess-based sandbox."),

        ("JS_DYNAMIC_REQUIRE",
         re.compile(r'\brequire\s*\(\s*(?:req\.|params\.|query\.|body\.)'),
         HIGH, "CWE-706", None,
         "Dynamic require() with user-controlled path enables path traversal and module injection.",
         "Whitelist allowed module names; never pass user input directly to require()."),

        ("JS_SERIALIZE_JS",
         re.compile(r'\bserialize\s*\([^)]*(?:req\.|params\.|query\.|body\.)'),
         HIGH, "CWE-502", None,
         "serialize-javascript or similar with user-controlled objects can produce executable code.",
         "Never serialize user-supplied objects for client transmission without sanitization."),
    ]

# HTTP input markers per language
HTTP_MARKERS = {
    ".py":   re.compile(r'request\.(body|data|json|POST|GET|form|files|args)|flask\.request|tornado\.web|django\.http'),
    ".java": re.compile(r'HttpServletRequest|@RequestBody|@RequestParam|getParameter\(|getInputStream\(|@PathVariable'),
    ".php":  re.compile(r'\$_(?:GET|POST|REQUEST|COOKIE|FILES)|php://input'),
    ".rb":   re.compile(r'\bparams\[|request\.body|request\.params'),
    ".js":   re.compile(r'\breq\.(body|params|query|headers)|request\.(body|params)'),
    ".ts":   re.compile(r'\breq\.(body|params|query|headers)|request\.(body|params)'),
    ".go":   re.compile(r'r\.Body|r\.URL\.Query\(\)|r\.FormValue\(|r\.Header\.Get\('),
}

SAFE_PATTERNS = {
    ".py": re.compile(r'yaml\.safe_load|Loader\s*=\s*yaml\.SafeLoader|ast\.literal_eval'),
    ".rb": re.compile(r'YAML\.safe_load|JSON\.parse'),
    ".java": re.compile(r'ValidatingObjectInputStream|addPermission'),
}

def scan_file(filepath: Path) -> list[Finding]:
    suffix = filepath.suffix.lower()
    if suffix not in PATTERNS:
        return []
    try:
        lines = filepath.read_text(encoding="utf-8", errors="replace").splitlines()
    except (OSError, PermissionError):
        return []

    full_text = "\n".join(lines)
    findings: list[Finding] = []
    http_marker = HTTP_MARKERS.get(suffix)
    safe_pat = SAFE_PATTERNS.get(suffix)

    for (name, pat, base_sev, cwe, cve, desc, fix) in PATTERNS[suffix]:
        for m in pat.finditer(full_text):
            lineno = full_text[:m.start()].count("\n") + 1

            # Skip if a safe alternative is on the same line
            line_text = lines[lineno - 1]
            if safe_pat and safe_pat.search(line_text):
                continue

            # Check for HTTP taint in surrounding ±40 lines
            start = max(0, lineno - 40)
            end = min(len(lines), lineno + 40)
            context = "\n".join(lines[start:end])
            has_http = bool(http_marker and http_marker.search(context)) if http_marker else False

            # Downgrade severity if no HTTP taint visible
            sev = base_sev
            if not has_http:
                if sev == CRITICAL:
                    sev = HIGH
                elif sev == HIGH:
                    sev = MEDIUM

            findings.append(Finding(
                file=str(filepath),
                line=lineno,
                pattern_name=name,
                matched_text=line_text.strip()[:120],
                severity=sev,
                cwe=cwe,
                cve=cve,
                description=desc,
                fix=fix,
                has_http_taint=has_http,
            ))

    return findings

def walk_files(root: Path, exts: set[str]) -> list[Path]:
    skip_dirs = {".git", "node_modules", "vendor", "__pycache__", ".venv", "venv",
                 "dist", "build", "target", ".gradle", ".mvn"}
    results = []
    for dirpath, dirnames, filenames in os.walk(root):
        dirnames[:] = [d for d in dirnames if d not in skip_dirs]
        for fname in filenames:
            if Path(fname).suffix.lower() in exts:
                results.append(Path(dirpath) / fname)
    return results

SEV_ORDER = {CRITICAL: 0, HIGH: 1, MEDIUM: 2, INFO: 3}
ICONS = {CRITICAL: "🔴", HIGH: "🟠", MEDIUM: "🟡", INFO: "⚪"}

def format_text_report(findings: list[Finding], scanned: int) -> str:
    by_sev: dict[str, list[Finding]] = {CRITICAL: [], HIGH: [], MEDIUM: [], INFO: []}
    for f in findings:
        by_sev[f.severity].append(f)

    lines = [
        "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
        "  DESERIALIZATION AUDIT REPORT (OWASP A08:2021)",
        "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
        f"  Scanned: {scanned} files",
        f"  Findings: {len(by_sev[CRITICAL])} CRITICAL  {len(by_sev[HIGH])} HIGH  {len(by_sev[MEDIUM])} MEDIUM",
        "",
    ]

    for sev in [CRITICAL, HIGH, MEDIUM]:
        group = by_sev[sev]
        if not group:
            continue
        lines.append(f"{ICONS[sev]} {sev} ({len(group)} findings)")
        lines.append("")
        for f in sorted(group, key=lambda x: x.file):
            rel = os.path.relpath(f.file)
            taint = "⚡ HTTP taint confirmed" if f.has_http_taint else "⚠️  HTTP taint unconfirmed — verify source"
            lines += [
                f"  {rel}:{f.line} — {f.pattern_name}",
                f"  Code:   {f.matched_text}",
                f"  Taint:  {taint}",
                f"  Risk:   {f.description}",
                f"  {f.cwe}" + (f"  {f.cve}" if f.cve else ""),
                f"  Fix:    {f.fix}",
                "",
            ]

    critical_count = len(by_sev[CRITICAL])
    lines += [
        "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
        f"  CI gate exit code: {'1 (CRITICAL findings present)' if critical_count else '0 (clean)'}",
        "  Full audit docs: https://owasp.org/A08_2021-Software_and_Data_Integrity_Failures/",
        "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
    ]
    return "\n".join(lines)

def main():
    parser = argparse.ArgumentParser(description="OWASP A08:2021 deserialization auditor")
    parser.add_argument("path", nargs="?", default=".", help="Root directory to scan")
    parser.add_argument("--json", action="store_true", help="JSON output")
    parser.add_argument("--ci", action="store_true", help="Exit 1 if CRITICAL found")
    args = parser.parse_args()

    root = Path(args.path).resolve()
    supported_exts = {".py", ".java", ".php", ".rb", ".js", ".ts", ".go"}
    files = walk_files(root, supported_exts)

    all_findings: list[Finding] = []
    for f in files:
        all_findings.extend(scan_file(f))
    all_findings.sort(key=lambda x: (SEV_ORDER[x.severity], x.file, x.line))

    if args.json:
        import dataclasses
        print(json.dumps([dataclasses.asdict(f) for f in all_findings], indent=2))
    else:
        print(format_text_report(all_findings, len(files)))

    if args.ci:
        has_critical = any(f.severity == CRITICAL for f in all_findings)
        sys.exit(1 if has_critical else 0)

if __name__ == "__main__":
    main()
```

## Usage

**Scan current directory:**
```bash
python3 audit_deserial.py
```

**Scan a specific project:**
```bash
python3 audit_deserial.py ~/projects/myapp
```

**JSON output (pipe to jq):**
```bash
python3 audit_deserial.py --json | jq '[.[] | select(.severity == "CRITICAL")]'
```

**CI fail-gate (exits 1 if any CRITICAL):**
```bash
python3 audit_deserial.py --ci && echo "Clean" || echo "CRITICAL deserialization risks found"
```

**GitHub Actions integration:**
```yaml
- name: Deserialization Audit
  run: |
    python3 .claude/skills/phy-deserialization-audit/audit_deserial.py --ci
```

## Sample Output

```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  DESERIALIZATION AUDIT REPORT (OWASP A08:2021)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  Scanned: 84 files
  Findings: 2 CRITICAL  3 HIGH  1 MEDIUM

🔴 CRITICAL (2 findings)

  api/views.py:145 — PICKLE_LOADS
  Code:   data = pickle.loads(request.body)
  Taint:  ⚡ HTTP taint confirmed
  Risk:   pickle.load/loads deserializes arbitrary Python objects — RCE if input is user-controlled.
  CWE-502
  Fix:    Never deserialize user input with pickle. Use json.loads() + Pydantic/marshmallow.

  api.php:89 — PHP_UNSERIALIZE_INPUT
  Code:   $obj = unserialize($_POST['data']);
  Taint:  ⚡ HTTP taint confirmed
  Risk:   unserialize() on HTTP input enables PHP Object Injection (POP chains → RCE).
  CWE-502
  Fix:    Never call unserialize() on user input. Use json_decode() with strict schema validation.

🟠 HIGH (3 findings)

  src/loader.java:34 — OBJECT_INPUT_STREAM
  Code:   Object obj = new ObjectInputStream(socket.getInputStream()).readObject();
  Taint:  ⚡ HTTP taint confirmed
  Risk:   Java deserialization via ObjectInputStream enables gadget-chain attacks.
  CWE-502
  Fix:    Use ValidatingObjectInputStream from Apache Commons IO or replace with JSON/Protobuf.

  config/parser.py:28 — YAML_UNSAFE_LOAD
  Code:   cfg = yaml.load(config_str)
  Taint:  ⚠️  HTTP taint unconfirmed — verify source
  Risk:   yaml.load() without SafeLoader executes arbitrary Python code embedded in YAML.
  CWE-502  CVE-2017-18342
  Fix:    Replace with yaml.safe_load(data) or yaml.load(data, Loader=yaml.SafeLoader).

  app.js:67 — JS_EVAL
  Code:   const result = eval(req.query.expr);
  Taint:  ⚡ HTTP taint confirmed
  Risk:   eval() with user-controlled input leads to arbitrary JS execution.
  CWE-95
  Fix:    Remove eval(). Use JSON.parse() for data, or a proper expression parser.

🟡 MEDIUM (1 finding)

  lib/api.rb:12 — RUBY_JSON_LOAD
  Code:   obj = JSON.load(params[:data])
  Taint:  ⚡ HTTP taint confirmed
  Risk:   JSON.load() creates Ruby objects from JSON — prefer JSON.parse.
  CWE-502
  Fix:    Replace with JSON.parse() which only produces standard types.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  CI gate exit code: 1 (CRITICAL findings present)
  Full audit docs: https://owasp.org/A08_2021-Software_and_Data_Integrity_Failures/
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```

## CVE/CWE Reference

| Vulnerability | CVE | CVSS | Impact |
|---------------|-----|------|--------|
| Java deserialization gadget chains | Multiple | 9.8 CRITICAL | RCE |
| Python pickle RCE | — | 9.8 CRITICAL | RCE |
| XStream XML deserialization | CVE-2021-29505 | 8.8 HIGH | RCE |
| SnakeYAML / PyYAML unsafe load | CVE-2017-18342 | 9.8 CRITICAL | RCE |
| Jackson polymorphic deserialization | CVE-2017-7525 | 9.8 CRITICAL | RCE |
| PHP Object Injection (POP chains) | Multiple | 9.0 CRITICAL | RCE |

## Relationships to Other phy- Skills

| Companion Skill | When to Use Together |
|----------------|---------------------|
| `phy-jwt-auth-audit` | Auth + deserialization both present in API layers |
| `phy-cors-audit` | Network boundary misconfig + input handling risks |
| `phy-regex-audit` | Before deploying: full input-handling security sweep |
| `phy-env-doctor` | Ensure no deserialization keys/secrets in .env |

Files in this skill

  • SKILL.md23.7 KB
  • _meta.json302 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.