What's Changed
🔒 Security Fixes
This release addresses a critical security vulnerability that was responsibly reported by @whoopsi-daisy.
Anchorr's Discord webhook endpoint accepted arbitrary POST requests without verifying the sender or the structure of the request. The handler parsed the payload and forwarded several fields directly into the internal execution pipeline used by the job runner. Those fields were later interpolated into a command string that was executed through a shell context. Because the values from the webhook payload were not sanitized or escaped, it was possible to craft a payload that terminated the expected argument sequence and injected additional shell tokens. A specially crafted webhook request could therefore alter the command that the job runner executed, allowing arbitrary command execution under the privileges of the Anchorr service.
Fixes included in this release:
- Webhook authentication — Anchorr now auto-generates a
WEBHOOK_SECRETon first start. All incoming webhook requests must include a validX-Webhook-Secretheader or they will be rejected with a 401. - Timing-safe secret comparison — Replaced naive string comparison with
crypto.timingSafeEqualto prevent timing attacks. - URL injection fix —
buildJellyfinUrlnow always uses the configuredJELLYFIN_BASE_URLand ignores anyServerUrlprovided in the webhook payload, preventing URL injection via poisoned metadata. - Credential leak fix — Removed debug logs that exposed token prefixes to log files.
- Secrets encoded at rest — Sensitive fields in
config.json(tokens, API keys, secrets) are now base64-encoded. Existing configs are migrated automatically on next save. - Rate limiting — Added a rate limiter (60 req/min) to the webhook endpoint.
- Config validation — Config is now validated against a Joi schema on startup.
⚠️ Breaking Changes
1. Webhook secret required
The webhook endpoint now requires an X-Webhook-Secret header. Requests without a valid secret are rejected with a 401. The secret is auto-generated on first start and shown in the Anchorr dashboard with a copy button and setup instructions.
2. Switch to Generic Destination required
Jellyfin does not support custom HTTP headers for the Discord Destination type. Since Anchorr now requires an X-Webhook-Secret header on every request, the Discord Destination can no longer be used. You must delete your existing Discord Destination and recreate it as a Generic Destination — otherwise the header cannot be set and all webhook deliveries will be rejected with 401 Unauthorized.
Migration
- Pull the new image and restart Anchorr
- Open the Anchorr dashboard and copy your
WEBHOOK_SECRET - In your Jellyfin webhook plugin, delete your existing Discord Destination and create a new Generic Destination
- Enter the Anchorr webhook URL in the Webhook URL field and configure your notification types as before
- Scroll down to the Headers section, click Add Header, set the name to
X-Webhook-Secret, and paste your secret as the value - Save