Skip to content

CVE-2026-63720: Verified Reproduction

CVE-2026-63720: datamodel-code-generator code injection via customBasePath before 0.70.0

CVE-2026-63720 is verified against datamodel-code-generator · pip. Affected versions: >= 0.11.6, < 0.70.0. Fixed in 0.70.0. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00302.

REPRO-2026-00302 datamodel-code-generator · pip Jul 27, 2026 CVE entry ↗ .txt
Severity
HIGH
Confidence
HIGH
Reproduced in
13m 44s
Tool calls
227
Spend
$2.62
01 · Overview

What Is CVE-2026-63720?

CVE-2026-63720 is a high-severity vulnerability affecting datamodel-code-generator >= 0.11.6, < 0.70.0. Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00302).

02 · Severity & CVSS

CVE-2026-63720 Severity

CVE-2026-63720 is rated high severity.

HIGH threat level
03 · Affected Versions

Affected datamodel-code-generator Versions

datamodel-code-generator · pip versions >= 0.11.6, < 0.70.0 are affected.

How to Reproduce CVE-2026-63720

$ pruva-verify REPRO-2026-00302
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00302/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-63720

Remote code execution — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

Malicious JSON Schema customBasePath extension value containing a newline plus a dot-free Python expression (print(...) writing a marker file), supplied to the datamodel-codegen CLI.

Attack chain
  1. datamodel-codegen CLI --input malicious_schema.json --input-file-type jsonschema --output <generated.py>; importing the generated module executes the injected print() at module top level.
How the agent worked 545 events · 227 tool calls · 14 min
14 minDuration
227Tool calls
140Reasoning steps
545Events
12Dead-ends
Agent activity over 14 min
Policy
1
Support
14
Repro
213
Judge
100
Variant
212
Verify
1
0:0013:44

Root Cause and Exploit Chain for CVE-2026-63720

Versions: datamodel-code-generator before 0.70.0; vulnerable

datamodel-code-generator (koxudaxi) before 0.70.0 contains a code-injection vulnerability in its JSON Schema customBasePath extension handling. The generator emits the schema-controlled customBasePath value verbatim into the generated module's from <module> import <name> statement (and the model base-class list) via Import.from_full_path, which splits the value on . and performs no identifier validation. A malicious schema whose customBasePath embeds a newline plus a dot-free Python expression causes attacker-controlled Python statements to execute when a victim imports the generated module. The vulnerability is reachable through the user-facing datamodel-codegen CLI generator input-to-output path.

  • Package/component affected: datamodel_code_generator parser (src/datamodel_code_generator/parser/jsonschema.py, JsonSchemaObject.custom_base_path / Import.from_full_path / Parser._resolve_base_class).
  • Affected versions: datamodel-code-generator before 0.70.0; vulnerable checkout anchored at af435d9893d1352924a2f011a2eb04a997d8b978 (parent of the fix, reported as 0.69.0).
  • Risk level: High. An attacker who supplies a JSON Schema to a victim running the generator achieves arbitrary Python code execution in the victim's import context when the generated module is imported. The generator output is the natural consumption path of the CLI, so the weaponized module is a direct product artifact.

Impact Parity

  • Disclosed/claimed maximum impact: Arbitrary code execution (code injection on import of generated output).
  • Reproduced impact from this run: Arbitrary attacker-controlled Python code execution. Two fresh vulnerable datamodel-codegen process instances generated modules whose import executed attacker-controlled print(...) writing distinct marker files (DMCG_RCE_INST1, DMCG_RCE_INST2). The fixed build rejected the same schema with exit code 2 and produced no output and no marker.
  • Parity: full.
  • Not demonstrated: None. The requested code-execution impact was demonstrated directly through the CLI entrypoint and the generated-module import consumption path.

Root Cause

JsonSchemaObject models the JSON Schema customBasePath extension as custom_base_path: str | list[str] | None (alias customBasePath) with no validation. The parser resolves a model's base class via Parser._resolve_base_class(class_name, obj.custom_base_path), which returns the raw custom_base_path string. That string is fed to Import.from_full_path, which splits the value on . to derive a module path and an import name, and the result is emitted verbatim into the generated from <module> import <name> statement and the model base-class list. Because the value is never checked to be a dotted Python identifier path, a value containing a newline plus attacker-controlled Python text is written into the generated module. Importing that module executes the injected text at module top level.

A leading comma in the payload (pydantic.BaseModel,\n<expr>) is used so that both the generated from pydantic import BaseModel,\n<expr> statement and the class ExploitModel(BaseModel,\n<expr>): definition stay syntactically valid (the newline is a continuation inside the comma-separated lists), letting the module compile and the injected <expr> execute on import.

The fix commit 545a96c56d1b6a8dd3f4a16c9090d8a4648d1e43 ("Merge commit from fork") adds a field_validator("custom_base_path", mode="before") that calls _validate_schema_python_import_path (which delegates to _validate_dotted_python_identifier_path) on every scalar/list member. Values that are not dotted Python identifier paths raise Error: customBasePath must be a dotted Python identifier path: ..., the CLI exits with code 2, and no output file is written.

Reproduction Steps

  1. Script: bundle/repro/reproduction_steps.sh.
  2. What the script does:
    • Reads bundle/project_cache_context.json and reuses the prepared repo at /pruva/project-cache/repo when prepared=true; otherwise clones the repository.
    • Resolves the vulnerable commit af435d98... (parent of fix) and the fixed commit 545a96c5..., and verifies the validate_custom_base_path validator is absent in the vulnerable checkout and present in the fixed checkout.
    • Creates two isolated git worktrees (one per commit) and editable-installs each into its own venv so the two builds cannot mutate each other's source tree.
    • Generates a malicious JSON Schema whose customBasePath is pydantic.BaseModel,\nprint('<MARKER>', file=open('<path>','w')) (the injected expression is dot-free so it survives Import.from_full_path's split-on-.).
    • Runs the real datamodel-codegen CLI (--input ... --input-file-type jsonschema --output ...) on the schema for two fresh vulnerable process instances, then imports each generated module; the injected print() executes at module top level and writes a distinct marker file.
    • Runs the same CLI against the fixed build; it rejects the schema with exit code 2, writes no output, and produces no marker (negative control).
    • Writes bundle/repro/runtime_manifest.json and bundle/repro/negative_control_observation.json.
  3. Expected evidence of reproduction: marker files repro/marker_inst1 (DMCG_RCE_INST1) and repro/marker_inst2 (DMCG_RCE_INST2) written by the vulnerable imports; repro/generated_vuln_inst1.py showing the injected print(...) at module level; fixed codegen log showing the rejection; negative_control_observation.json showing no marker and exit code 2.

Evidence

  • bundle/logs/reproduction_steps.log — full run transcript (verdict CONFIRMED).
  • bundle/logs/vuln_codegen_inst1.log, bundle/logs/vuln_codegen_inst2.log — vulnerable CLI codegen output (exit 0).
  • bundle/logs/vuln_import_inst1.log, bundle/logs/vuln_import_inst2.log — generated-module import output (executes injected print, then TypeError from the None base class).
  • bundle/logs/fixed_codegen.log — fixed CLI rejection (exit 2, customBasePath must be a dotted Python identifier path).
  • bundle/repro/malicious_schema_inst1.json, bundle/repro/malicious_schema_inst2.json, bundle/repro/malicious_schema_fixed.json — attacker-controlled schemas.
  • bundle/repro/generated_vuln_inst1.py — generated module containing the injected print('DMCG_RCE_INST1', file=open(...,'w')) at line 9.
  • bundle/repro/marker_inst1 = DMCG_RCE_INST1, bundle/repro/marker_inst2 = DMCG_RCE_INST2 — proof of executed attacker-controlled code.
  • bundle/repro/negative_control_observation.json — fixed build: exit 2, no output, no marker.
  • bundle/repro/runtime_manifest.json — runtime evidence manifest.

Key excerpt from generated_vuln_inst1.py:

from __future__ import annotations

from pydantic import BaseModel

print('DMCG_RCE_INST1', file=open('/workspace/bundle/repro/run/marker_inst1', 'w'))


class ExploitModel(
    BaseModel,
    print('DMCG_RCE_INST1', file=open('/workspace/bundle/repro/run/marker_inst1', 'w')),
):
    value: str | None = None

Environment: CPython 3.14 on Linux x86_64; datamodel-code-generator 0.69.0 (vulnerable) and 0.69.1.dev1+g545a96c56 (fixed); pydantic v2.

Recommendations / Next Steps

  • Upgrade guidance: Upgrade datamodel-code-generator to 0.70.0 or later, which contains the customBasePath field_validator.
  • Suggested fix approach: The applied fix is appropriate: validate every scalar and list member of customBasePath (and the analogous customTypePath / x-python-import paths) with _validate_dotted_python_identifier_path before any code generation, and reject non-identifier values with a clear error and no output. Consider also hardening the generator's import/base-class emission to escape or reject control characters (newlines) in schema-controlled identifier fields as defense in depth.
  • Testing recommendations: Add regression tests that feed schemas with newline-bearing and non-identifier customBasePath/customTypePath values and assert the CLI exits non-zero with no output; the fix commit already adds unsafe_custom_base_path_scalar.json / unsafe_custom_base_path_list_nested.json fixtures for this.

Additional Notes

  • Idempotency confirmation: The script was run twice consecutively; both runs exited 0 with CONFIRMED=1, both vulnerable instances wrote their distinct markers, and the fixed build rejected the schema both times. The script recreates worktrees/venvs each run, so it is self-cleaning.
  • Why the comma is required: Without the leading comma in the payload, class ExploitModel(BaseModel\n<expr>): becomes a SyntaxError (two expressions with no separator inside the base-class list), which prevents the whole module from compiling and blocks execution of the injected statement. The comma keeps both the import and the class definition syntactically valid so the module compiles and the injected statement executes at import time; the subsequent TypeError (metaclass conflict from the None returned by print) occurs after the marker is written.
  • Why the expression must be dot-free: Import.from_full_path splits the raw customBasePath string on ., so any . in the injected text (including dots inside string literals) breaks the payload across segments and the final segment no longer contains the intended expression. The print(..., file=open(...)) form is dot-free.
  • Surface match: The claim surface is cli_local / cli_command. The reproduction exercises the real datamodel-codegen CLI entrypoint (scripts.datamodel-codegen = "datamodel_code_generator.__main__:main") end-to-end and demonstrates the claimed code-execution impact through the generated-module import consumption path.

CVE-2026-63720 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:000:33
0:00
session startedaccounts/fireworks/routers/glm-5p2-fast · CVE-2026-63720 · REPRO-20
0:02
0:03
web search
0:05
web search
0:07
0:08
0:09
0:20
0:20
extract_facts
no facts extracted
0:21
0:21
0:21
supportclaim_contract
0:26
0:26
0:26
0:26
0:27
0:27
0:27
0:27
error

Unknown error

0:28
0:28
0:28
0:28
0:30
0:30
0:30
0:30
$ls -la /pruva/project-cache/ 2>&1 | head -50; echo "---"; ls -la /pruva/project-cache/repo 2>&1 | head -20; echo "---"; ls -la /pruva/project-cache/repo-mirrors 2>&1 | head
0.2s
EXPLOIT-KNOWLEDGE PREFLIGHT REVIEW REQUIRED: `run_shell` is blocked until this agent calls `review_exploit_knowledge_preflight` with snapshot_sha256 `7b2a523bb1bf53b1582f9c277ddec23bbc844a23d9bead643fad6a2bb302d8e0` and exactly one disposition for each candidate ID [33e92eae-aa49-41c0-b366-328175437821, 5172d9af-1f3d-4643-97dc-1080a146006c]. Historical records remain reference-only; review their typed mechanisms and preconditions before continuing.
0:30
error

Unknown error

0:33
08 · How to Fix

How to Fix CVE-2026-63720

Upgrade datamodel-code-generator · pip to 0.70.0 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-63720 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-63720

Is CVE-2026-63720 exploitable?

Yes. Pruva independently reproduced CVE-2026-63720 in datamodel-code-generator and verified the exploit fires end-to-end in a sandboxed environment. A runnable proof-of-concept script and the full agent transcript are on this page (reproduction REPRO-2026-00302).

How severe is CVE-2026-63720?

CVE-2026-63720 is rated high severity.

What type of vulnerability is CVE-2026-63720?

CVE-2026-63720 is classified as CWE-94 Improper Control of Generation of Code ('Code Injection') (Improper Control of Generation of Code ('Code Injection')).

Which versions of datamodel-code-generator are affected by CVE-2026-63720?

datamodel-code-generator >= 0.11.6, < 0.70.0 is affected by CVE-2026-63720.

Is there a fix for CVE-2026-63720?

Yes. CVE-2026-63720 is fixed in datamodel-code-generator 0.70.0. Upgrading to the fixed version remediates the issue.

How can I reproduce CVE-2026-63720?

Pruva provides a verified reproduction script on this page. Download it and run it inside an isolated environment such as a container or virtual machine — never against production. The reproduction was confirmed end-to-end by Pruva's automated agents.

Is the CVE-2026-63720 reproduction verified?

Yes. Pruva reproduced CVE-2026-63720 with high confidence in a sandboxed environment, capturing the full agent transcript and artifacts as evidence.
11 · References

References for CVE-2026-63720

Authoritative sources for CVE-2026-63720 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.