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
+15 -14
View File
@@ -39,27 +39,28 @@ And **SDNQ** improvements: now with *NPU* support and its own native *attention*
- **Internal**
- delay init of video models
- **Fixes**
- sdnq: warn instead of error for triton
- amd: hipBLASLt improved detection, thanks @0xDELUXA
- api: add missing endpoint registration
- api: openapi schema exposure
- api: stricter api request and response schemas
- caption: button in standard-ui
- embeddings: handle textual-inversion with new transformers
- insightface: missing dependencies
- pulid: import paths
- processors: init code and multiple fixes
- live preview: configurable pause when not in focus, thanks @Artheriax
- log: strip ansi sequences from ring buffer and client side logging
- measure: handle current kanvas stage
- model metadata: handle invalid metadata and strip workflows
- mps: install `torchsde` as requirement
- vae: scale factor improved detection
- ui: networks details scrollbars
- onnxruntime: handle invalid version
- onnxruntime: mark all import paths as non-critical
- measure: handle current kanvas stage
- caption: button in standard-ui
- python: experimental/ignore version checks
- hipBLASLt: improved detection, thanks @0xDELUXA
- embeddings: handle textual-inversion with new transformers
- options: handle compatibility options
- log: strip ansi sequences from ring buffer and client side logging
- model metadata: handle invalid metadata and strip workflows
- processors: init code and multiple fixes
- pulid: import paths
- python: experimental/ignore version checks
- sdnq: warn instead of error for `triton`
- ui debounce aspect-ratio linked width/height controls
- api: stricter api request and response schemas
- api: openapi schema exposure
- ui: networks details scrollbars
- vae: scale factor improved detection
## Update for 2026-06-16
+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}")