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>
327 lines
10 KiB
Markdown
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
|
|
```
|