api audit

Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
Vladimir Mandic
2026-07-01 11:47:29 +02:00
parent f4dd9bd063
commit 6fc0830d2c
4 changed files with 46 additions and 17 deletions
+10 -2
View File
@@ -64,7 +64,9 @@ class Api:
self.add_api_route("/sdapi/v1/txt2img", self.generate.post_text2img, methods=["POST"], response_model=models.ResTxt2Img, tags=["Generation"])
self.add_api_route("/sdapi/v1/img2img", self.generate.post_img2img, methods=["POST"], response_model=models.ResImg2Img, tags=["Generation"])
self.add_api_route("/sdapi/v1/control", self.control.post_control, methods=["POST"], response_model=control.ResControl, tags=["Generation"])
self.add_api_route("/sdapi/v1/process", self.process.extras_single_image_api, methods=["POST"], response_model=models.ResProcessImage, tags=["Processing"])
self.add_api_route("/sdapi/v1/extra-single-image", self.process.extras_single_image_api, methods=["POST"], response_model=models.ResProcessImage, tags=["Processing"])
self.add_api_route("/sdapi/v1/process-batch", self.process.extras_batch_images_api, methods=["POST"], response_model=models.ResProcessBatch, tags=["Processing"])
self.add_api_route("/sdapi/v1/extra-batch-images", self.process.extras_batch_images_api, methods=["POST"], response_model=models.ResProcessBatch, tags=["Processing"])
self.add_api_route("/sdapi/v1/preprocess", self.process.post_preprocess, methods=["POST"], tags=["Processing"])
self.add_api_route("/sdapi/v1/mask", self.process.post_mask, methods=["POST"], tags=["Processing"])
@@ -80,23 +82,29 @@ class Api:
self.add_api_route("/sdapi/v1/preprocessors", self.process.get_preprocess, methods=["GET"], response_model=list[process.ItemPreprocess])
self.add_api_route("/sdapi/v1/masking", self.process.get_mask, methods=["GET"], response_model=process.ItemMask)
self.add_api_route("/sdapi/v1/samplers", endpoints.get_samplers, methods=["GET"], response_model=list[models.ItemSampler])
self.add_api_route("/sdapi/v1/schedulers", endpoints.get_schedulers, methods=["GET"], response_model=list[models.ItemScheduler])
self.add_api_route("/sdapi/v1/upscalers", endpoints.get_upscalers, methods=["GET"], response_model=list[models.ItemUpscaler])
self.add_api_route("/sdapi/v1/sd-models", endpoints.get_sd_models, methods=["GET"], response_model=list[models.ItemModel])
self.add_api_route("/sdapi/v1/controlnets", endpoints.get_controlnets, methods=["GET"], response_model=list[str])
self.add_api_route("/sdapi/v1/control-models", endpoints.get_control_models, methods=["GET"], response_model=list[str])
self.add_api_route("/sdapi/v1/control-modes", endpoints.get_control_modes, methods=["GET"], response_model=dict[str, list[str]])
self.add_api_route("/sdapi/v1/face-restorers", endpoints.get_restorers, methods=["GET"], response_model=list[models.ItemDetailer])
self.add_api_route("/sdapi/v1/detailers", endpoints.get_detailers, methods=["GET"], response_model=list[models.ItemDetailer])
self.add_api_route("/sdapi/v1/prompt-styles", endpoints.get_prompt_styles, methods=["GET"], response_model=list[models.ItemStyle])
self.add_api_route("/sdapi/v1/embeddings", endpoints.get_embeddings, methods=["GET"], response_model=models.ResEmbeddings)
self.add_api_route("/sdapi/v1/ip-adapters", endpoints.get_ip_adapters, methods=["GET"], response_model=list[str])
self.add_api_route("/sdapi/v1/wildcards", endpoints.get_wildcards, methods=["GET"], response_model=list[dict], tags=["Enumerators"])
self.add_api_route("/sdapi/v1/sd-vae", endpoints.get_sd_vaes, methods=["GET"], response_model=list[models.ItemVae])
self.add_api_route("/sdapi/v1/extensions", endpoints.get_extensions_list, methods=["GET"], response_model=list[models.ItemExtension])
self.add_api_route("/sdapi/v1/extra-networks", endpoints.get_extra_networks, methods=["GET"], response_model=list[models.ItemExtraNetwork])
self.add_api_route("/sdapi/v1/extra-network-detail", endpoints.get_extra_network_detail, methods=["GET"], response_model=models.ItemExtraNetworkFull)
self.add_api_route("/sdapi/v1/extra-network-details", endpoints.get_extra_network_details, methods=["GET"], response_model=models.ResExtraNetworkDetails)
self.add_api_route("/sdapi/v1/unets", endpoints.get_unets, methods=["GET"], response_model=list[models.ItemUNet])
# functional api
self.add_api_route("/sdapi/v1/file", endpoints.get_file, methods=["GET"], tags=["Functional"])
self.add_api_route("/sdapi/v1/delete-image", endpoints.get_deleteimage, methods=["GET"], tags=["Functional"])
self.add_api_route("/sdapi/v1/delete-file", endpoints.get_deletefile, methods=["GET"], tags=["Functional"])
self.add_api_route("/sdapi/v1/delete-image", endpoints.get_deleteimage, methods=["DELETE"], tags=["Functional"])
self.add_api_route("/sdapi/v1/delete-file", endpoints.get_deletefile, methods=["DELETE"], tags=["Functional"])
self.add_api_route("/sdapi/v1/png-info", endpoints.get_pnginfo, methods=["GET"], response_model=models.ResImageInfo, tags=["Functional"])
self.add_api_route("/sdapi/v1/png-info", endpoints.post_pnginfo, methods=["POST"], response_model=models.ResImageInfo, tags=["Functional"])
self.add_api_route("/sdapi/v1/checkpoint", endpoints.get_checkpoint, methods=["GET"], tags=["Functional"])
+14 -1
View File
@@ -210,7 +210,14 @@ def get_schedulers():
"""List all available schedulers with their class names and options."""
from modules.sd_samplers import list_samplers
all_schedulers = list_samplers()
return all_schedulers
return [
{
'name': scheduler.name,
'cls': scheduler.constructor.__name__ if scheduler.constructor is not None else None,
'options': scheduler.options,
}
for scheduler in all_schedulers
]
def post_unload_checkpoint():
"""Unload the current model and refiner from memory to free VRAM."""
@@ -331,6 +338,8 @@ def get_file(file: str):
import os
from pathlib import Path
from starlette.responses import FileResponse
if shared.demo is None:
raise HTTPException(status_code=503, detail="server not ready")
allowed_dirs = shared.demo.allowed_paths
if not file.strip():
raise HTTPException(status_code=400, detail="file path is required")
@@ -345,6 +354,8 @@ def get_file(file: str):
def get_deletefile(file: str):
import os
from pathlib import Path
if shared.demo is None:
raise HTTPException(status_code=503, detail="server not ready")
allowed_dirs = shared.demo.allowed_paths
if file is None or len(file.strip()) == 0:
raise HTTPException(status_code=400, detail="file path is required")
@@ -368,6 +379,8 @@ def get_deletefile(file: str):
def get_deleteimage(file: str):
import os
from pathlib import Path
if shared.demo is None:
raise HTTPException(status_code=503, detail="server not ready")
allowed_dirs = shared.demo.allowed_paths
if file is None or len(file.strip()) == 0:
raise HTTPException(status_code=400, detail="file path is required")
+7
View File
@@ -1,5 +1,6 @@
import os
import time
from pathlib import Path
from fastapi import Request, Depends, BackgroundTasks, Response
from fastapi.exceptions import HTTPException
from fastapi.responses import FileResponse
@@ -13,6 +14,12 @@ def get_js(request: Request):
file = request.query_params.get("file", None)
if (file is None) or (len(file) == 0):
raise HTTPException(status_code=400, detail="file parameter is required")
# Security: validate path is within allowed directories
if shared.demo is None:
raise HTTPException(status_code=503, detail="server not ready")
allowed_dirs = shared.demo.allowed_paths
if not any(Path(folder).absolute() in Path(file).absolute().parents for folder in allowed_dirs):
raise HTTPException(status_code=403, detail=f"file {file}: must be in one of allowed directories")
ext = file.split('.')[-1]
if ext not in ['js', 'css', 'map', 'html', 'wasm', 'ttf', 'mjs', 'json']:
raise HTTPException(status_code=400, detail=f"invalid file extension: {ext}")