{"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.40803,"cwe":["CWE-306"],"ingestedAt":"2026-08-25T16:28:50.978Z","slug":"CVE-2026-55640","body":"## Overview\n\n## Summary\nThe `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.\n\n## Details\n**Vulnerable file:** `nextcloud_mcp_server/vector/webhook_receiver.py`, function `handle_nextcloud_webhook()`, **lines 55-67**\n\n**Root cause 1**: Auth check is guarded by `if secret`: - skipped entirely when `WEBHOOK_SECRET` is unset.\n\n**Root cause 2**: `webhook_secret: str | None = None` in config - no startup validator enforces it, even when vector sync is enabled.\n\n**Trusted field**: `payload[\"user\"][\"uid\"]` in `webhook_parser.py` is used as-is for all Qdrant operations - no cross-check against an authenticated session.\n\n`webhook_receiver.py`, **lines 55-67**:\n```python\nsecret = get_settings().webhook_secret  # None by default\nif secret:                           # skipped entirely when unset\n    ... validate Bearer header ...\nelse:\n    _warn_missing_secret_once()     # just logs, still processes\n```\n`webhook_parser.py`, **line 57**:\n```python\nuser_id = payload[\"user\"][\"uid\"]     # attacker-controlled\n```\n## PoC\n**No credentials required**. Works on any deployment where `WEBHOOK_SECRET` is not explicitly set (the default).\n```json\nPOST /webhooks/nextcloud\nContent-Type: application/json\n\n{\n  \"event\": {\n    \"class\": \"OCP\\\\Files\\\\Events\\\\Node\\\\BeforeNodeDeletedEvent\",\n    \"node\": { \"path\": \"/victim/files/Notes/any.md\", \"id\": 12345 }\n  },\n  \"user\": { \"uid\": \"victim\" },\n  \"time\": 0\n}\n```\n**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.\n\n\n## Impact\n+ Anyone on the network with access to port `8000` - no credentials needed.\n+ Attacker can delete or trigger re-index of any user's vector embeddings in Qdrant by spoofing `user.uid` in the payload.\n+ Mass-sending delete events for all doc IDs destroys the entire semantic search index for all users, requiring a full re-scan to recover.\n\n## Recommend Fix\n1. Enforce `WEBHOOK_SECRET` at startup ( file `config_validators.py` )\n```python\nif vector_sync_enabled and not settings.webhook_secret:\n    raise ConfigurationError(\n        \"WEBHOOK_SECRET must be set when vector sync is enabled\"\n    )\n```\n2. Reject requests when secret is unset ( file `webhook_receiver.py` )\n```python\nsecret = get_settings().webhook_secret\nif not secret:\n    return JSONResponse({\"status\": \"unavailable\"}, status_code=503)\nprovided = request.headers.get(\"authorization\", \"\").encode()\nif not hmac.compare_digest(provided, f\"Bearer {secret}\".encode()):\n    return JSONResponse({\"status\": \"unauthorized\"}, status_code=401)\n```\n\n## Affected packages\n\n- `nextcloud-mcp-server < 0.117.2`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `nextcloud-mcp-server 0.117.2`","depth":"midnight","depthScore":50,"depthScoreParts":{"impact":50.1,"likelihood":0.1,"exploitation":0,"ransomware":0},"changes":[]}