Is hiyouga/LlamaFactory safe?
- Python dynamic code execution
- Python shell/command execution
- Python filesystem write/delete
What to do: Nothing here argues against installing it. Grant the capabilities it lists only if you expect the tool to need them.
hiyouga/LlamaFactory is a PyPI package analyzed by SkillTotal's deterministic static scanner. The scan found no malicious indicators, though 1 risky construct is reported for review. It can: dynamic code execution, filesystem read, filesystem write, network egress and shell execution — capabilities are what the code can do, not a verdict on intent. Risk score 10/100 (low).
llamafactory
Automated static-analysis result. It can contain false positives and false negatives, and is not a claim about the intent of hiyouga/LlamaFactory's authors. Report a false positive.
Behavioral traits
How this component maps to the CSA agentic threat model. Descriptive — it never affects the risk score.
Findings (6)
The code turns strings into live code at runtime (eval / new Function / exec).
Evaluator().eval()
Why it matters: If those strings aren't fixed and trusted, they become a way to run arbitrary code.
Fix: Avoid evaluating dynamically constructed code; if unavoidable, ensure the input is a trusted constant and never derived from external data.
The component can run operating-system commands or spawn processes.
commit_info = subprocess.run(["git", "rev-parse", "HEAD"], capture_output=True, text=True, check=True)
process = subprocess.run(
(
"torchrun --nnodes {rdzv_nnodes} --nproc-per-node {nproc_per_node} "
"--rdzv-id {rdzv_id} --rdzv-backend c10d --rdzv-endpoint {master_addr}:{master_port} " …process = subprocess.run(
(
"torchrun --nnodes {nnodes} --node_rank {node_rank} --nproc_per_node {nproc_per_node} "
"--master_addr {master_addr} --master_port {master_port} {file_name} …process = subprocess.run(
torchrun_args + script_args,
env=env,
check=True,
)self.trainer = Popen(
["llamafactory-cli", "train", save_cmd(args)],
env=env,
stdout=webui_log,
stderr=webui_log,
text=True,
)Why it matters: Powerful and often legitimate — confirm the commands aren't built from untrusted input.
Fix: Confirm the command and its arguments are fully controlled and not derived from untrusted input; avoid shell=True.
A server is bound to all network interfaces (0.0.0.0), not just your own machine.
api_host = os.getenv("API_HOST", "0.0.0.0")api_host = os.getenv("API_HOST", "0.0.0.0")server_name = os.getenv("GRADIO_SERVER_NAME", "[::]" if gradio_ipv6 else "0.0.0.0")server_name = os.getenv("GRADIO_SERVER_NAME", "[::]" if gradio_ipv6 else "0.0.0.0")server_name = os.getenv("GRADIO_SERVER_NAME", "[::]" if gradio_ipv6 else "0.0.0.0")Why it matters: Without authentication, other hosts on the network can reach it.
Fix: Bind to 127.0.0.1 for local-only use, or require authentication and restrict access if remote exposure is intended.
The component reads files from disk.
with open(os.path.join(input_dir, CONFIG_NAME), encoding="utf-8") as f:
with open(os.path.join(input_dir, CONFIG_NAME), encoding="utf-8") as f:
with open(os.path.join("saves", "test_mfu", "all_results.json"), encoding="utf-8") as f:image_stream = open(image_url, "rb")
with open(config_path) as f:
with open(mapping, encoding="utf-8") as f:
with open(os.path.join(save_dictionary, TRAINER_STATE_NAME), encoding="utf-8") as f:
with open(self.extra_config, encoding="utf-8") as f:
mca_config = json.load(open(os.path.join(model_args.model_name_or_path, "mca_config.json")))
with open(cfg_path, encoding="utf-8") as f:
with open(os.path.join(ckpt_dir, "metadata.json")) as f:
with open(config_file, encoding="utf-8") as f:
with open(index_file) as f:
with open(index_file) as f:
with open(_get_config_path(), encoding="utf-8") as f:
with open(os.path.join(dataset_dir, DATA_CONFIG), encoding="utf-8") as f:
with open(config_path, encoding="utf-8") as f:
with open(path, encoding="utf-8") as f:
with open(os.path.join(dataset_dir, DATA_CONFIG), encoding="utf-8") as f:
with open(file_path, encoding="utf-8") as f:
with open(os.path.join(dataset_dir, DATA_CONFIG), encoding="utf-8") as f:
with open(running_log_path, encoding="utf-8") as f:
with open(trainer_log_path, encoding="utf-8") as f:
with open(swanlab_config_path, encoding="utf-8") as f:
with open(webui_log_path, "rb") as f:
Why it matters: Usually legitimate, but worth confirming it can't be steered into reading sensitive files.
Fix: Confirm which files are read and that paths cannot be influenced by untrusted input to reach sensitive locations.
The component writes or deletes files on disk.
with open(os.path.join(output_dir, index_name), "w", encoding="utf-8") as f:
with open(os.path.join(output_dir, CONFIG_NAME), "w", encoding="utf-8") as f:
with open(os.path.join(output_dir, index_name), "w", encoding="utf-8") as f:
with open(os.path.join(output_dir, CONFIG_NAME), "w", encoding="utf-8") as f:
with open("predictions_score.json", "w", encoding="utf-8") as f:with open(os.path.join(output_dir, index_name), "w", encoding="utf-8") as f:
shutil.copy(source_file, os.path.join(save_path, extra_file))
shutil.copy(source_file, os.path.join(save_path, extra_file))
with open(save_name, "w", encoding="utf-8") as f:
with open(save_name, "w", encoding="utf-8") as f:
with open(matrix_save_name, "w", encoding="utf-8") as f:
with open(os.path.join(self.eval_args.save_dir, "results.json"), "w", encoding="utf-8", newline="\n") as f:
with open(os.path.join(self.eval_args.save_dir, "results.log"), "w", encoding="utf-8", newline="\n") as f:
os.remove(self.running_log)
with open(self.running_log, "a", encoding="utf-8") as f:
os.remove(path_to_checkpoint)
with open(os.path.join(output_dir, TRAINER_LOG), "a", encoding="utf-8") as f:
os.remove(os.path.join(args.output_dir, TRAINER_LOG))
os.remove(index_path)
with open(path, "w", encoding="utf-8") as f:
os.remove(done_file)
with open(done_file, "w", encoding="utf-8") as f:
with open(output_prediction_file, "w", encoding="utf-8") as writer:
with open(output_prediction_file, "w", encoding="utf-8") as f:
with open(os.path.join(args.output_dir, SWANLAB_CONFIG), "w") as f:
Why it matters: Usually legitimate, but worth confirming the paths can't be controlled by untrusted input.
Fix: Confirm which files are written/deleted and that paths cannot be influenced by untrusted input.
The component makes outbound network requests.
import requests
image_stream = requests.get(image_url, stream=True).raw
video_stream = requests.get(video_url, stream=True).raw
audio_stream = requests.get(audio_url, stream=True).raw
import requests
response = requests.get(f"{self.base_url}/get_model_info", timeout=5)response = requests.post(f"{self.base_url}/generate", json=json_data, stream=True)import requests
response = requests.post(server_url, json=payload, headers=headers)
Why it matters: Usually legitimate, but confirm the destinations are expected and no sensitive data leaves.
Fix: Confirm the destination hosts are expected and that no sensitive data is sent off-host.
How attackers abuse these capabilities
Interactive labs on the attack class behind the rules above. They show the technique, not anything found in hiyouga/LlamaFactory.
Check your own component
Run the same evidence-backed scan on any MCP server, agent skill, or package.
Scan your own componentHow we determine this: deterministic static analysis (regex + AST), evidence-anchored, no code execution. Methodology →