NewApp Monitoring is liveSign up free
AgentbeamBeta
AI agent vulnerability · Other MCP servers and tools

CVE-2026-55640: nextcloud-mcp-server: Unauthenticated `POST /webhooks/nextcloud` allows arbitrary vector data deletion when `WEBHOOK_SECRET` is unset ( default )

criticalCVSS 3.1 9.1GHSA-8vh3-g2qg-2h2c

nextcloud-mcp-server up to 0.117.1 accepts unauthenticated `POST /webhooks/nextcloud` requests because `WEBHOOK_SECRET` is unset by default. Anyone who can reach the server's port can delete or force re-indexing of any user's Qdrant vector embeddings. Upgrade to 0.117.2 and set `WEBHOOK_SECRET`.

Published Aug 25, 2026 · Analysis Oct 25, 2026

What an attacker can do

In nextcloud_mcp_server/vector/webhook_receiver.py, handle_nextcloud_webhook() validates a Bearer header only if secret:. The setting webhook_secret defaults to None, and no startup check requires it, even when vector sync is on. With no secret, the code logs a one-time warning and processes the request anyway.

The handler then trusts the body. webhook_parser.py reads payload["user"]["uid"] and uses it for all Qdrant operations, with no check against a session. An attacker posts a fake BeforeNodeDeletedEvent naming a victim's uid and a document id. Qdrant deletes that document's embeddings. Looping over ids wipes a user's index, and looping over users wipes the whole semantic search index. Recovery needs a full rescan.

The attacker needs only network access to the server port (8000 in the advisory) and no credentials. The result is lost integrity and availability of the search index, not file contents. Agents that rely on this MCP server for semantic search over Nextcloud notes will return empty or stale results until the index is rebuilt.

Who is affected

nextcloud-mcp-server 0.117.1 and earlier, in deployments where WEBHOOK_SECRET is not set. That is the default. Deployments with the secret set already require a Bearer header and reject others, although the advisory frames the default as the flaw.

Check the version and the environment:

pip show nextcloud-mcp-server
env | grep WEBHOOK_SECRET

For a container, run the same checks inside it.

nextcloud-mcp-server

Ecosystem
PyPI
Affected
<= 0.117.1
Fixed in
0.117.2
Check
pip show nextcloud-mcp-server

How to fix it

Upgrade to 0.117.2:

pip install --upgrade nextcloud-mcp-server

The fix commit makes the secret mandatory for webhooks. If WEBHOOK_SECRET is unset, the /webhooks/nextcloud route is not mounted (404), startup logs a warning, and vector sync falls back to the polling scanner. A request that still reaches the receiver gets a 503, and webhook registration through /api/v1/webhooks returns 503. With a secret set, requests without a valid Authorization: Bearer header get 401.

To keep webhook-driven sync, set WEBHOOK_SECRET to a long random value (the docs suggest secrets.token_urlsafe(32)). Re-register existing webhooks afterwards, since the sources do not say how old unauthenticated ones are handled. Do not keep the docker-compose placeholder dev-webhook-secret-change-me outside local development.

How to tell if you were hit

Check access logs for POST /webhooks/nextcloud requests from addresses other than your Nextcloud server, or without an Authorization header. On vulnerable versions the log holds a one-time warning about a missing webhook secret.

In Qdrant, look for sudden drops in point counts per user and for delete operations you did not trigger. A search that suddenly returns nothing for a user is a symptom. Port 8000 should not be reachable from untrusted networks.

Original advisory

From the GitHub Advisory Database (CC BY 4.0).

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:

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:

user_id = payload["user"]["uid"]     # attacker-controlled

PoC

No credentials required. Works on any deployment where WEBHOOK_SECRET is not explicitly set (the default).

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 )
if vector_sync_enabled and not settings.webhook_secret:
    raise ConfigurationError(
        "WEBHOOK_SECRET must be set when vector sync is enabled"
    )
  1. Reject requests when secret is unset ( file webhook_receiver.py )
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)

Credited to fortress07.

Questions

Do I need to be logged in to exploit CVE-2026-55640?

No. The endpoint accepts any POST when WEBHOOK_SECRET is unset, and the attacker supplies the target user.uid in the JSON body.

Can the attacker read my Nextcloud files through this?

The advisory describes deletion and forced re-indexing of Qdrant vector embeddings only. It does not describe reading file contents. The damage is to the semantic search index.

Will webhooks still work after I upgrade to 0.117.2?

Only if WEBHOOK_SECRET is set. Without it the route returns 404 and sync uses the polling scanner instead.

References

How this page is made: the data panel comes from the GitHub Advisory Database, CISA’s KEV catalog, the NVD and FIRST EPSS, and updates twice a day. The analysis is written by an AI model from the sources listed above and checked by automated rules before it is published. Check versions against the vendor's advisory before you act. Methodology

More Other MCP servers and tools vulnerabilities

All Other MCP servers and tools entries