Introduction
The pz_minimax.py file handles MiniMax prompt management for a ComfyUI plugin, exposing HTTP endpoints that let users create, update, and delete prompts stored in a shared JSON file. But a flaw in the delete_minimax_prompt function at line 79 created a serious security risk: the DELETE /pz_easyuse/minimax-prompts/{index} route had zero authentication or access control, meaning any device on the same network could silently delete every stored prompt.
This isn't a theoretical concern. ComfyUI instances are frequently exposed on local networks — and sometimes the open internet — for collaborative AI workflows. A single unauthenticated DELETE endpoint turns a productivity tool into a target for data destruction.
The Vulnerability Explained
What the code did (before the fix)
Here's the vulnerable endpoint as it existed at line 79 of pz_minimax.py:
@PromptServer.instance.routes.delete("/pz_easyuse/minimax-prompts/{index}")
async def delete_minimax_prompt(request):
try:
index = int(request.match_info["index"])
except ValueError:
...
The function immediately parses the {index} path parameter and proceeds to delete the prompt at that position from a shared JSON file. There is no check on:
- Who is making the request (no authentication)
- Where the request originates (no IP filtering)
- Whether the caller owns the prompt (no ownership verification)
How an attacker exploits this
The attack is trivially simple and requires no special tools — just curl or a basic script:
# Enumerate and delete all prompts by sequential index
for i in $(seq 0 100); do
curl -X DELETE http://<comfyui-host>:8188/pz_easyuse/minimax-prompts/$i
done
Because prompts are stored in a shared JSON file and indices are sequential integers starting from 0, an attacker can:
- Enumerate all prompts by sending DELETE requests with incrementing indices
- Destroy every stored prompt in seconds
- Repeat the attack silently — there's no logging or rate limiting
This is a classic 2-step exploitation chain: network access → data destruction. The scanner flagged it as "Likely exploitable" because the endpoint is directly reachable over HTTP with no intervening middleware.
Real-world impact
For ComfyUI users, MiniMax prompts represent carefully crafted AI generation configurations. Losing them means:
- Lost work: Hours of prompt engineering wiped out
- No recovery: If no external backup exists, the prompts are gone permanently
- Silent attack: Users may not realize prompts were deleted until they try to use them
- Lateral risk: If this endpoint lacks auth, other endpoints in the same plugin may also be unprotected
The Fix
The PR introduces a focused, minimal change: a localhost-only guard function that blocks all non-local requests to the DELETE endpoint.
New helper function
A new _is_local_request function was added at line 79:
def _is_local_request(request):
return request.remote in ("127.0.0.1", "::1")
This checks the request.remote property (which aiohttp populates with the client's IP address) against the IPv4 loopback (127.0.0.1) and IPv6 loopback (::1) addresses.
Before vs. After
Before (vulnerable):
@PromptServer.instance.routes.delete("/pz_easyuse/minimax-prompts/{index}")
async def delete_minimax_prompt(request):
try:
index = int(request.match_info["index"])
except ValueError:
...
After (fixed):
def _is_local_request(request):
return request.remote in ("127.0.0.1", "::1")
@PromptServer.instance.routes.delete("/pz_easyuse/minimax-prompts/{index}")
async def delete_minimax_prompt(request):
if not _is_local_request(request):
return web.json_response({"error": "Forbidden"}, status=403)
try:
index = int(request.match_info["index"])
except ValueError:
...
Why this works
- Early rejection: The guard runs before any business logic, so unauthorized requests never reach the prompt deletion code
- HTTP 403 response: Returns a proper
Forbiddenstatus code with a JSON error body, maintaining API consistency - Covers both IP versions: Checks both
127.0.0.1(IPv4) and::1(IPv6) to handle dual-stack configurations - Minimal blast radius: The change is scoped to exactly one file and one code path — the destructive DELETE endpoint — preserving all existing behavior for legitimate local users
- Reusable guard: The
_is_local_requesthelper is extracted as a standalone function, making it easy to apply to other sensitive endpoints in the same file
Design consideration
For a local development tool like ComfyUI, localhost restriction is an appropriate security boundary. The tool is designed to run on a user's machine, so restricting destructive operations to local requests prevents network-based attacks while maintaining the expected user experience.
Prevention & Best Practices
1. Apply authentication to all state-changing endpoints
Every endpoint that modifies data (POST, PUT, DELETE, PATCH) should have explicit access control. In aiohttp, you can use middleware to enforce this globally:
@web.middleware
async def auth_middleware(request, handler):
if request.method in ("DELETE", "PUT", "PATCH"):
if not _is_local_request(request):
return web.json_response({"error": "Forbidden"}, status=403)
return await handler(request)
2. Audit all routes in your application
If one endpoint lacked authentication, others likely do too. Run a systematic audit:
# Find all route registrations in the codebase
grep -rn "@PromptServer.instance.routes" *.py
3. Use defense in depth
Don't rely on a single control. Layer your defenses:
- Network-level: Bind to
127.0.0.1instead of0.0.0.0if the service is local-only - Application-level: IP checks, authentication tokens, CSRF protection
- Data-level: Backups, soft deletes instead of hard deletes, audit logging
4. Consider CSRF protection for browser-accessible APIs
If the ComfyUI interface is accessed via a browser, a malicious webpage could trigger DELETE requests using the user's browser (which would originate from localhost). Adding CSRF tokens provides an additional layer of protection.
5. Reference standards
- OWASP Top 10 A01:2021: Broken Access Control
- CWE-306: Missing Authentication for Critical Function
- CWE-862: Missing Authorization
Key Takeaways
- The
delete_minimax_promptfunction at line 79 ofpz_minimax.pyhad no access control, allowing any network client to delete prompts by guessing sequential integer indices — a trivially exploitable pattern. - Sequential integer IDs as the sole resource identifier make enumeration attacks effortless — an attacker doesn't need to know anything about the data to delete all of it.
- Localhost-only restriction via
request.remotechecking is an effective baseline for local development tools, but should be combined with additional controls for any network-exposed deployment. - Extracting the
_is_local_requesthelper as a reusable function makes it easy to consistently apply the same guard to other sensitive endpoints likeupdate_minimax_prompt. - Destructive operations (DELETE) deserve the strictest access controls — they should be the first endpoints audited in any security review.
How Orbis AppSec Detected This
- Source: HTTP DELETE request from any network-connected client to
/pz_easyuse/minimax-prompts/{index}, with the{index}path parameter as attacker-controlled input - Sink: The prompt deletion logic inside
delete_minimax_prompt()inpz_minimax.py:79, which removes entries from the shared JSON prompt store - Missing control: No authentication, no IP-based access restriction, and no ownership verification before executing the destructive delete operation
- CWE: CWE-306 — Missing Authentication for Critical Function
- Fix: Added a
_is_local_request()guard that checksrequest.remoteagainst loopback addresses and returns HTTP 403 Forbidden for all non-local requests
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
This vulnerability is a textbook example of how a single missing access control check can turn a useful feature into an attack vector. The delete_minimax_prompt endpoint in pz_minimax.py did exactly what it was supposed to do — delete prompts — but it did it for anyone who asked, not just authorized users.
The fix is small (6 lines of code) but meaningful: it establishes a security boundary that didn't exist before. For developers building similar local-first tools with HTTP APIs, the lesson is clear: every state-changing endpoint needs explicit access control, even if you think the tool is only used locally. Networks are shared, ports get forwarded, and assumptions about who can reach your service are often wrong.