Files
mcp-nextcloud/docs/webhook-management-guide.md
Chris CoutinhoandClaude Opus 4.8 4fc2b10945 fix(security): require WEBHOOK_SECRET for the Nextcloud webhook receiver
GHSA-8vh3-g2qg-2h2c (CVSS 9.1, CWE-306): POST /webhooks/nextcloud had no
authentication when WEBHOOK_SECRET was unset (the default). The receiver
trusted the attacker-supplied user.uid and fed it to Qdrant, letting an
unauthenticated network caller delete or re-index any user's vector
embeddings.

Webhooks now require WEBHOOK_SECRET end-to-end:

- app.py: the /webhooks/nextcloud route is only mounted when WEBHOOK_SECRET
  is set; otherwise it 404s and a startup warning notes vector sync falls
  back to the polling scanner.
- webhook_receiver.py: removed the warn-and-accept fallback. No secret -> 503,
  missing/invalid bearer -> 401; the payload is never processed unauthenticated.
- webhook_routes.py / api/webhooks.py: webhook_auth_pair() raises
  WebhookSecretNotConfigured instead of returning authMethod="none"; both
  registration entry points return a clear 503 so no dead unauthenticated
  webhooks are created.

Also expose webhooks availability to the Astrolabe UI via GET /api/v1/status
("webhooks_enabled": bool), set WEBHOOK_SECRET on the docker-compose
semantic-search dev services, and update env.sample + ADR-010 / ADR-018 /
webhook-management-guide docs.

Vector sync still works without a secret via the polling scanner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 18:18:34 +02:00

327 lines
10 KiB
Markdown

# Webhook Management Guide
This guide explains how to enable and disable webhooks for vector sync in each MCP server deployment mode. Webhooks enable near-real-time synchronization of content changes to the vector database, complementing the default polling-based sync.
**Related ADRs:**
- ADR-010: Webhook-Based Vector Sync
- ADR-020: Deployment Modes and Configuration Validation
## Prerequisites
Before enabling webhooks, ensure:
1. **Nextcloud 30+** with `webhook_listeners` app enabled
2. **[Astrolabe app](https://github.com/cbcoutinho/astrolabe)** installed in Nextcloud (provides settings UI and credentials API)
3. **MCP server** accessible from Nextcloud via HTTP(S)
4. **Vector sync enabled** on the MCP server
## Webhook Architecture Overview
The webhook system has two components:
1. **Webhook Registration** - Configuring Nextcloud to send change notifications to the MCP server
2. **Background Sync Credentials** - Allowing the MCP server to access Nextcloud APIs on behalf of users
Both must be configured for webhooks to function properly.
## Deployment Mode Specifics
### 1. Single-User BasicAuth
**Configuration:**
```bash
NEXTCLOUD_HOST=http://localhost:8080
NEXTCLOUD_USERNAME=admin
NEXTCLOUD_PASSWORD=password
VECTOR_SYNC_ENABLED=true
```
**Enable Webhooks:**
1. Register webhooks using occ commands (requires Nextcloud admin):
```bash
# Enable webhook_listeners app
php occ app:enable webhook_listeners
# Register webhooks for vector sync
php occ webhook_listeners:add \
--event "OCP\Files\Events\Node\NodeCreatedEvent" \
--uri "http://mcp-server:8000/webhooks/nextcloud" \
--method POST
# Repeat for other events (see Event Types below)
```
2. Optionally reduce polling frequency:
```bash
VECTOR_SYNC_SCAN_INTERVAL=86400 # 24 hours
```
**Disable Webhooks:**
```bash
# List registered webhooks
php occ webhook_listeners:list
# Remove specific webhook by ID
php occ webhook_listeners:remove <webhook-id>
```
**Notes:**
- Simplest mode - admin credentials used for all operations
- No per-user provisioning required
- Background sync runs as the configured admin user
---
### 2. Multi-User BasicAuth Pass-Through
**Configuration:**
```bash
NEXTCLOUD_HOST=http://nextcloud.example.com
MCP_DEPLOYMENT_MODE=multi_user_basic
ENABLE_BACKGROUND_OPERATIONS=true
TOKEN_ENCRYPTION_KEY=<key>
TOKEN_STORAGE_DB=/app/data/tokens.db
VECTOR_SYNC_ENABLED=true
# OAuth client for Astrolabe API access
NEXTCLOUD_OIDC_CLIENT_ID=<client-id>
NEXTCLOUD_OIDC_CLIENT_SECRET=<client-secret>
```
**Credential Architecture:**
This mode uses **two separate credential mechanisms**:
1. **OAuth Session** (for management API access, including webhooks):
- Obtained via browser OAuth flow (`/oauth/login`)
- Stores refresh token in MCP server's `tokens.db`
- Used for webhook registration/management APIs
2. **App Password** (for background sync):
- Generated in Nextcloud Security settings
- Stored encrypted in Nextcloud's `oc_preferences` via Astrolabe
- Used by background scanners to access Nextcloud APIs
**Enable Webhooks:**
#### Step 1: Complete OAuth Login (for Management API)
Users must authorize the MCP server to access their Nextcloud:
1. Navigate to **Nextcloud Settings → Astrolabe** (Personal settings)
2. Click **"Authorize via OAuth"** under "Option 1"
3. Complete OAuth consent flow
4. Verify the page shows "Background Sync Access: Active"
#### Step 2: Configure App Password (for Background Sync)
Since OAuth refresh tokens have short expiry, users should also configure an app password:
1. Navigate to **Nextcloud Settings → Security**
2. Generate a new app password (name it "Astrolabe" or "MCP Server")
3. Return to **Nextcloud Settings → Astrolabe**
4. Under "Option 2: App Password", paste the app password
5. Click **Save**
#### Step 3: Register Webhooks (Admin)
Same as Single-User BasicAuth:
```bash
php occ webhook_listeners:add \
--event "OCP\Files\Events\Node\NodeCreatedEvent" \
--uri "http://mcp-server:8003/webhooks/nextcloud" \
--method POST
```
**Disable Webhooks:**
*Per-User:*
1. Navigate to **Nextcloud Settings → Astrolabe**
2. Click **"Revoke Access"** (for OAuth tokens) or **"Revoke Access"** (for app password)
*System-Wide:*
```bash
php occ webhook_listeners:remove <webhook-id>
```
**Troubleshooting:**
If OAuth login fails with "Access forbidden - Your client is not authorized":
1. Check if OAuth client is registered:
```sql
SELECT id, name, client_identifier FROM oc_oidc_clients
WHERE dcr = 1 ORDER BY id DESC LIMIT 5;
```
2. Restart MCP server to trigger DCR re-registration
3. Verify `NEXTCLOUD_OIDC_CLIENT_ID` and `NEXTCLOUD_OIDC_CLIENT_SECRET` are set
If background sync fails with "User no longer provisioned":
1. Verify app password is stored:
```sql
SELECT userid, configkey FROM oc_preferences
WHERE appid = 'astrolabe' AND userid = 'username';
```
2. Ensure user completed **both** OAuth login AND app password setup
---
### 3. OAuth Single-Audience (Default OAuth Mode)
**Configuration:**
```bash
NEXTCLOUD_HOST=http://nextcloud.example.com
# No NEXTCLOUD_USERNAME/PASSWORD
ENABLE_BACKGROUND_OPERATIONS=true
TOKEN_ENCRYPTION_KEY=<key>
TOKEN_STORAGE_DB=/app/data/tokens.db
VECTOR_SYNC_ENABLED=true
```
**Enable Webhooks:**
#### Step 1: User Provisioning
Users authorize via OAuth with `offline_access` scope:
1. MCP client initiates OAuth flow
2. User consents to requested scopes including `offline_access`
3. MCP server stores refresh token for background operations
Alternatively, via Astrolabe UI:
1. Navigate to **Nextcloud Settings → Astrolabe**
2. Click **"Authorize via OAuth"**
3. Complete consent flow
#### Step 2: Register Webhooks (Admin)
```bash
php occ webhook_listeners:add \
--event "OCP\Files\Events\Node\NodeCreatedEvent" \
--uri "http://mcp-server:8001/webhooks/nextcloud" \
--method POST
```
**Disable Webhooks:**
*Per-User:*
- Via Astrolabe UI: Click "Disable Indexing" or "Disconnect"
- Via MCP tool: Use `revoke_nextcloud_access` if available
*System-Wide:*
```bash
php occ webhook_listeners:remove <webhook-id>
```
---
### 4. Smithery Stateless
**Configuration:**
- Configuration from session URL params
- `VECTOR_SYNC_ENABLED=false` (required)
**Webhooks:**
**Not supported.** This mode is stateless with no persistent storage or background operations.
---
## Webhook Event Types
Register these webhook events for full vector sync coverage:
### File/Note Events
```bash
# Use BeforeNodeDeletedEvent for deletions (includes node.id)
php occ webhook_listeners:add --event "OCP\Files\Events\Node\NodeCreatedEvent" --uri "$MCP_URL/webhooks/nextcloud"
php occ webhook_listeners:add --event "OCP\Files\Events\Node\NodeWrittenEvent" --uri "$MCP_URL/webhooks/nextcloud"
php occ webhook_listeners:add --event "OCP\Files\Events\Node\BeforeNodeDeletedEvent" --uri "$MCP_URL/webhooks/nextcloud"
```
### Calendar Events
```bash
php occ webhook_listeners:add --event "OCP\Calendar\Events\CalendarObjectCreatedEvent" --uri "$MCP_URL/webhooks/nextcloud"
php occ webhook_listeners:add --event "OCP\Calendar\Events\CalendarObjectUpdatedEvent" --uri "$MCP_URL/webhooks/nextcloud"
php occ webhook_listeners:add --event "OCP\Calendar\Events\CalendarObjectDeletedEvent" --uri "$MCP_URL/webhooks/nextcloud"
```
### Tables Events
```bash
php occ webhook_listeners:add --event "OCA\Tables\Event\RowAddedEvent" --uri "$MCP_URL/webhooks/nextcloud"
php occ webhook_listeners:add --event "OCA\Tables\Event\RowUpdatedEvent" --uri "$MCP_URL/webhooks/nextcloud"
php occ webhook_listeners:add --event "OCA\Tables\Event\RowDeletedEvent" --uri "$MCP_URL/webhooks/nextcloud"
```
## Security Considerations
### Webhook Authentication (required — GHSA-8vh3-g2qg-2h2c)
`WEBHOOK_SECRET` is **required** to use webhooks. The receiver trusts the
`user.uid` in the payload and feeds it to Qdrant, so an unauthenticated POST
could delete or re-index any user's embeddings. When `WEBHOOK_SECRET` is unset,
the `/webhooks/nextcloud` route is **not mounted** (404) and registration
refuses to create webhooks; vector sync still runs via the polling scanner.
```bash
# MCP Server (generate with: python -c "import secrets; print(secrets.token_urlsafe(32))")
WEBHOOK_SECRET=<generate-random-secret>
# Nextcloud webhook registration — the Authorization header is mandatory
php occ webhook_listeners:add \
--event "..." \
--uri "$MCP_URL/webhooks/nextcloud" \
--header "Authorization: Bearer <secret>"
```
### Token Storage
- Refresh tokens and app passwords are encrypted using `TOKEN_ENCRYPTION_KEY`
- Store the key securely (environment variable, secrets manager)
- Different users have isolated credential storage
## Monitoring
### MCP Server Logs
```bash
# Docker
docker compose logs mcp-multi-user-basic | grep -i webhook
# Key log messages
# - "Queued document from webhook: ..." - Success
# - "Webhook authentication failed" - Auth error
# - "User X no longer provisioned" - Missing credentials
```
### Nextcloud Logs
```bash
docker compose exec app cat /var/www/html/data/nextcloud.log | \
jq 'select(.message | contains("webhook"))' | tail
```
### Database Checks
```sql
-- Check registered webhooks
SELECT * FROM oc_webhook_listeners;
-- Check OAuth clients
SELECT id, name, token_type FROM oc_oidc_clients WHERE dcr = 1;
-- Check user credentials stored by Astrolabe app
SELECT userid, configkey FROM oc_preferences WHERE appid = 'astrolabe';
```
## Common Issues
### "Access forbidden - Your client is not authorized to connect"
**Cause:** OAuth client registration expired or not present in Nextcloud
**Fix:** Restart MCP server to trigger DCR re-registration
### "User X no longer provisioned, stopping scanner"
**Cause:** Background sync credentials missing or expired
**Fix:** User must complete credential provisioning (see mode-specific steps)
### "Failed to fetch" in browser console during OAuth
**Cause:** Network issue between browser and MCP server callback endpoint
**Fix:** Verify MCP server is accessible at the configured `NEXTCLOUD_MCP_SERVER_URL`
### Webhooks not firing
**Causes:**
1. `webhook_listeners` app not enabled
2. Webhook not registered for the event type
3. Background job workers not running
**Fix:**
```bash
php occ app:enable webhook_listeners
php occ background:cron # or configure systemd cron
```