
Proof-of-concept for CVE-2026-93349, an OS command injection in Frictionless <= 5.20.0rc1 explore CLI via malicious Data Package descriptor paths.
<= 5.20.0rc1 explore OS Command InjectionSeverity: High, CVSS 3.1 8.8
Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
Affected: all Frictionless versions <= 5.20.0rc1
Component: frictionless.console.commands.explore
CWE: CWE-78 (OS Command Injection), CWE-20 (Improper Input Validation)
Reported by: Sai Teja Erukude
Frictionless versions up to and including the 5.20.0rc1 prerelease contain an
OS command injection vulnerability in the frictionless explore CLI command.
The command loads a user-supplied Data Package descriptor, collects resource paths, derives , joins those paths into a single command string, and passes that string directly to :
resource.normpathos.systempaths = [resource.normpath for resource in resources if resource.normpath]
os.system(f"vd {' '.join(paths)}")
Because resources[*].path is descriptor-controlled metadata from
datapackage.json, an attacker can include shell metacharacters in a resource
path. When a victim runs frictionless explore on the malicious descriptor, the
shell interprets those metacharacters and executes the injected command.
An attacker may execute arbitrary commands in the security context of the user
running frictionless explore.
This can affect workflows where users inspect, validate, or explore Data Package descriptors from untrusted projects, archives, repositories, or shared datasets.
Depending on the local environment, this may allow:
The vulnerable path is the explore command:
frictionless explore <package>
-> Package.from_descriptor(...)
-> Resource.list()
-> resource.normpath
-> os.system(f"vd {' '.join(paths)}")
The PoC generates a Data Package descriptor where resources[0].path contains a
harmless marker-file write payload.
On Windows, the generated path uses & command separators:
data.csv & echo frictionless_explore_command_injection_triggered>"...\marker.txt" & rem
On POSIX systems, the generated path uses semicolon command separators:
data.csv; printf %s frictionless_explore_command_injection_triggered > .../marker.txt; #
The runner invokes the real frictionless explore command from the installed
package. It clears PATH for the child process so the intended vd viewer does
not launch interactively, but the injected command still executes when the shell
parses the constructed command string.
package_builder.py creates the attacker-controlled Data
Package descriptor and a benign CSV file.
run_poc.py validates the descriptor, invokes
frictionless explore, and prints execution evidence.
Run in a local test environment only:
python -m venv .venv
.venv\Scripts\activate
python -m pip install -r requirements.txt
python -B run_poc.py
The included requirements.txt pins frictionless==5.20.0rc1, the latest
confirmed vulnerable prerelease in this range.
Expected evidence on vulnerable versions:
frictionless_version : 5.20.0rc1
installed_package : True
descriptor_valid : True
listed_resource_count : 1
raw_path_contains_payload : True
normpath_contains_payload : True
explore_returncode : 0
viewer_launch_disabled : True
marker_exists : True
marker_contents
frictionless_explore_command_injection_triggered
success : True
The important evidence is that the malicious descriptor is valid, the
descriptor-controlled path survives into resource.normpath, and marker.txt
is created after frictionless explore runs.
The payload is intentionally harmless. It only writes this local marker string:
frictionless_explore_command_injection_triggered
It does not spawn a shell session, connect to a network service, read secrets, delete data, or modify files outside the PoC directory.
Avoid passing descriptor-controlled paths to a shell.
Replace the shell-based invocation with subprocess.run using an argument
vector:
subprocess.run(["vd", *paths], check=False)
This passes each resource path as a literal argument to vd. Shell
metacharacters inside a path remain ordinary characters inside that single argv
entry and are not executed.
Blacklisting specific path characters is weaker because shell syntax varies across platforms and shells. The command invocation boundary is the root cause, so the durable fix should avoid shell interpretation.
See fix.md for a suggested patch.
Discovered and reported by Sai Teja Erukude, coordinated through VulnCheck.
exploit_walkthrough.mdfix.md