{"id":"PYSEC-2026-3866","summary":"nextcloud-mcp-server: Unauthenticated `POST /webhooks/nextcloud` allows arbitrary vector data deletion when `WEBHOOK_SECRET` is unset ( default )","details":"## 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```","aliases":["CVE-2026-55640","GHSA-8vh3-g2qg-2h2c"],"modified":"2026-09-10T12:15:18.811964337Z","published":"2026-09-10T09:44:56.031196Z","references":[{"type":"WEB","url":"https://github.com/cbcoutinho/nextcloud-mcp-server/security/advisories/GHSA-8vh3-g2qg-2h2c"},{"type":"WEB","url":"https://github.com/cbcoutinho/nextcloud-mcp-server/commit/4fc2b10945108cf1008ec9698291de6706ffcb73"},{"type":"PACKAGE","url":"https://github.com/cbcoutinho/nextcloud-mcp-server"},{"type":"WEB","url":"https://github.com/cbcoutinho/nextcloud-mcp-server/tree/v0.117.2"},{"type":"PACKAGE","url":"https://pypi.org/project/nextcloud-mcp-server"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-8vh3-g2qg-2h2c"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-55640"}],"affected":[{"package":{"name":"nextcloud-mcp-server","ecosystem":"PyPI","purl":"pkg:pypi/nextcloud-mcp-server"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.117.2"}]}],"versions":["0.100.0","0.101.0","0.101.1","0.101.2","0.101.3","0.101.4","0.102.0","0.103.0","0.104.0","0.104.1","0.105.0","0.106.0","0.107.0","0.108.0","0.108.1","0.108.2","0.108.3","0.109.0","0.109.1","0.110.0","0.110.1","0.110.2","0.111.0","0.112.0","0.113.0","0.113.1","0.114.0","0.115.0","0.115.1","0.116.0","0.117.0","0.117.1","0.17.1","0.65.3","0.65.4","0.66.0","0.66.1","0.66.2","0.67.0","0.68.0","0.68.1","0.68.2","0.68.3","0.68.4","0.69.0","0.70.0","0.70.1","0.70.2","0.70.3","0.70.4","0.71.0","0.72.0","0.72.1","0.72.2","0.72.3","0.72.4","0.72.5","0.72.6","0.72.7","0.73.0","0.73.1","0.73.2","0.74.0","0.75.0","0.75.1","0.75.2","0.76.0","0.77.0","0.77.1","0.78.0","0.79.0","0.79.1","0.79.2","0.79.3","0.80.0","0.81.0","0.82.0","0.83.0","0.83.1","0.83.2","0.83.3","0.83.4","0.84.0","0.84.1","0.84.2","0.85.0","0.85.1","0.86.0","0.86.1","0.86.2","0.86.3","0.86.4","0.87.0","0.87.1","0.87.2","0.88.0","0.88.1","0.88.2","0.88.3","0.89.0","0.90.0","0.90.1","0.90.2","0.91.0","0.91.1","0.91.2","0.91.3","0.92.0","0.92.1","0.93.0","0.94.0","0.94.1","0.95.0","0.96.0","0.97.0","0.98.0","0.98.1","0.99.0"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/nextcloud-mcp-server/PYSEC-2026-3866.yaml"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H"}]}