mirror of
https://github.com/oobabooga/text-generation-webui.git
synced 2026-03-08 06:33:51 +01:00
Security: prevent path traversal in character/user/file save and delete
This commit is contained in:
parent
521ddbb722
commit
eba262d47a
|
|
@ -36,6 +36,7 @@ from modules.utils import (
|
|||
delete_file,
|
||||
get_available_characters,
|
||||
get_available_users,
|
||||
sanitize_filename,
|
||||
save_file
|
||||
)
|
||||
from modules.web_search import add_web_search_attachments
|
||||
|
|
@ -1557,12 +1558,12 @@ def upload_character(file, img_path, tavern=False):
|
|||
data = yaml.safe_load(decoded_file)
|
||||
|
||||
if 'char_name' in data:
|
||||
name = data['char_name']
|
||||
name = sanitize_filename(data['char_name'])
|
||||
greeting = data['char_greeting']
|
||||
context = build_pygmalion_style_context(data)
|
||||
yaml_data = generate_character_yaml(name, greeting, context)
|
||||
else:
|
||||
name = data['name']
|
||||
name = sanitize_filename(data['name'])
|
||||
yaml_data = generate_character_yaml(data['name'], data['greeting'], data['context'])
|
||||
|
||||
outfile_name = name
|
||||
|
|
@ -1653,6 +1654,7 @@ def generate_instruction_template_yaml(instruction_template):
|
|||
|
||||
|
||||
def save_character(name, greeting, context, picture, filename):
|
||||
filename = sanitize_filename(filename)
|
||||
if filename == "":
|
||||
logger.error("The filename is empty, so the character will not be saved.")
|
||||
return
|
||||
|
|
@ -1668,6 +1670,7 @@ def save_character(name, greeting, context, picture, filename):
|
|||
|
||||
|
||||
def delete_character(name, instruct=False):
|
||||
name = sanitize_filename(name)
|
||||
# Check for character data files
|
||||
for extension in ["yml", "yaml", "json"]:
|
||||
delete_file(shared.user_data_dir / 'characters' / f'{name}.{extension}')
|
||||
|
|
@ -1751,6 +1754,7 @@ def generate_user_yaml(name, user_bio):
|
|||
|
||||
def save_user(name, user_bio, picture, filename):
|
||||
"""Save user profile to YAML file"""
|
||||
filename = sanitize_filename(filename)
|
||||
if filename == "":
|
||||
logger.error("The filename is empty, so the user will not be saved.")
|
||||
return
|
||||
|
|
@ -1772,6 +1776,7 @@ def save_user(name, user_bio, picture, filename):
|
|||
|
||||
def delete_user(name):
|
||||
"""Delete user profile files"""
|
||||
name = sanitize_filename(name)
|
||||
# Check for user data files
|
||||
for extension in ["yml", "yaml", "json"]:
|
||||
delete_file(shared.user_data_dir / 'users' / f'{name}.{extension}')
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import traceback
|
|||
import gradio as gr
|
||||
|
||||
from modules import chat, presets, shared, ui, utils
|
||||
from modules.utils import gradio
|
||||
from modules.utils import gradio, sanitize_filename
|
||||
|
||||
|
||||
def create_ui():
|
||||
|
|
@ -91,6 +91,7 @@ def create_event_handlers():
|
|||
|
||||
def handle_save_preset_confirm_click(filename, contents):
|
||||
try:
|
||||
filename = sanitize_filename(filename)
|
||||
utils.save_file(str(shared.user_data_dir / "presets" / f"{filename}.yaml"), contents)
|
||||
available_presets = utils.get_available_presets()
|
||||
output = gr.update(choices=available_presets, value=filename)
|
||||
|
|
@ -106,6 +107,7 @@ def handle_save_preset_confirm_click(filename, contents):
|
|||
|
||||
def handle_save_confirm_click(root, filename, contents):
|
||||
try:
|
||||
filename = sanitize_filename(filename)
|
||||
utils.save_file(root + filename, contents)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
|
@ -115,6 +117,7 @@ def handle_save_confirm_click(root, filename, contents):
|
|||
|
||||
def handle_delete_confirm_click(root, filename):
|
||||
try:
|
||||
filename = sanitize_filename(filename)
|
||||
utils.delete_file(root + filename)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
|
|
|||
|
|
@ -15,6 +15,17 @@ def gradio(*keys):
|
|||
return [shared.gradio[k] for k in keys]
|
||||
|
||||
|
||||
def sanitize_filename(name):
|
||||
"""Strip path traversal components from a filename.
|
||||
|
||||
Returns only the final path component with leading dots removed,
|
||||
preventing directory traversal via '../' or absolute paths.
|
||||
"""
|
||||
name = Path(name).name # drop all directory components
|
||||
name = name.lstrip('.') # remove leading dots
|
||||
return name
|
||||
|
||||
|
||||
def _is_path_allowed(abs_path_str):
|
||||
"""Check if a path is under the project root or the configured user_data directory."""
|
||||
abs_path = Path(abs_path_str).resolve()
|
||||
|
|
|
|||
Loading…
Reference in a new issue