---
id: CVE-2026-55640
aliases:
  - GHSA-8vh3-g2qg-2h2c
  - PYSEC-2026-3866
title: >-
  nextcloud-mcp-server: Unauthenticated `POST /webhooks/nextcloud` allows
  arbitrary vector data deletion when `WEBHOOK_SECRET` is unset ( d…
summary: >-
  nextcloud-mcp-server: Unauthenticated `POST /webhooks/nextcloud` allows
  arbitrary vector data deletion when `WEBHOOK_SECRET` is unset ( default )
severity: critical
cvss: 9.1
cvssVector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H'
vendor: nextcloud-mcp-server
product: nextcloud-mcp-server
ecosystem: pip
affected:
  - nextcloud-mcp-server < 0.117.2
patched:
  - nextcloud-mcp-server 0.117.2
published: '2026-08-25'
updated: '2026-09-10'
sourceUpdated: '2026-09-10T12:26:01.466673851Z'
source: OSV
sourceUrl: 'https://osv.dev/vulnerability/GHSA-8vh3-g2qg-2h2c'
references:
  - url: >-
      https://github.com/cbcoutinho/nextcloud-mcp-server/security/advisories/GHSA-8vh3-g2qg-2h2c
  - url: >-
      https://github.com/cbcoutinho/nextcloud-mcp-server/commit/4fc2b10945108cf1008ec9698291de6706ffcb73
  - url: 'https://github.com/cbcoutinho/nextcloud-mcp-server'
  - url: 'https://github.com/cbcoutinho/nextcloud-mcp-server/tree/v0.117.2'
  - url: 'https://pypi.org/project/nextcloud-mcp-server'
  - url: 'https://github.com/advisories/GHSA-8vh3-g2qg-2h2c'
  - url: 'https://nvd.nist.gov/vuln/detail/CVE-2026-55640'
tags:
  - osv
  - pip
  - nvd
  - ghsa
epss: 0.00484
epssPercentile: 0.4082
cwe:
  - CWE-306
ingestedAt: '2026-08-25T16:28:50.978Z'
---

## Overview

## Summary
The `POST /webhooks/nextcloud` endpoint has no authentication by default: `WEBHOOK_SECRET` defaults to `None` and is never required by startup validation. When unset, the receiver accepts any unauthenticated POST. The `user_id` is taken directly from the attacker-supplied payload and passed to Qdrant, allowing an unauthenticated attacker to delete or corrupt vector embeddings for any user.

## Details
**Vulnerable file:** `nextcloud_mcp_server/vector/webhook_receiver.py`, function `handle_nextcloud_webhook()`, **lines 55-67**

**Root cause 1**: Auth check is guarded by `if secret`: - skipped entirely when `WEBHOOK_SECRET` is unset.

**Root cause 2**: `webhook_secret: str | None = None` in config - no startup validator enforces it, even when vector sync is enabled.

**Trusted field**: `payload["user"]["uid"]` in `webhook_parser.py` is used as-is for all Qdrant operations - no cross-check against an authenticated session.

`webhook_receiver.py`, **lines 55-67**:
```python
secret = get_settings().webhook_secret  # None by default
if secret:                           # skipped entirely when unset
    ... validate Bearer header ...
else:
    _warn_missing_secret_once()     # just logs, still processes
```
`webhook_parser.py`, **line 57**:
```python
user_id = payload["user"]["uid"]     # attacker-controlled
```
## PoC
**No credentials required**. Works on any deployment where `WEBHOOK_SECRET` is not explicitly set (the default).
```json
POST /webhooks/nextcloud
Content-Type: application/json

{
  "event": {
    "class": "OCP\\Files\\Events\\Node\\BeforeNodeDeletedEvent",
    "node": { "path": "/victim/files/Notes/any.md", "id": 12345 }
  },
  "user": { "uid": "victim" },
  "time": 0
}
```
**Result:** **Qdrant** deletes all vector embeddings for `victim` doc `12345` with **no authentication**. Attacker can loop over doc IDs for mass deletion.  All user targets accepted.


## Impact
+ Anyone on the network with access to port `8000` - no credentials needed.
+ Attacker can delete or trigger re-index of any user's vector embeddings in Qdrant by spoofing `user.uid` in the payload.
+ Mass-sending delete events for all doc IDs destroys the entire semantic search index for all users, requiring a full re-scan to recover.

## Recommend Fix
1. Enforce `WEBHOOK_SECRET` at startup ( file `config_validators.py` )
```python
if vector_sync_enabled and not settings.webhook_secret:
    raise ConfigurationError(
        "WEBHOOK_SECRET must be set when vector sync is enabled"
    )
```
2. Reject requests when secret is unset ( file `webhook_receiver.py` )
```python
secret = get_settings().webhook_secret
if not secret:
    return JSONResponse({"status": "unavailable"}, status_code=503)
provided = request.headers.get("authorization", "").encode()
if not hmac.compare_digest(provided, f"Bearer {secret}".encode()):
    return JSONResponse({"status": "unauthorized"}, status_code=401)
```

## Affected packages

- `nextcloud-mcp-server < 0.117.2`

## Remediation

Upgrade to a patched release:

- `nextcloud-mcp-server 0.117.2`
