Security fixes are provided for the current 1.x release line.
| Version line | Supported |
|---|---|
| 1.x | Yes |
| 0.x and earlier pre-release builds | No |
If you are still on a 0.x build, upgrade before reporting a security issue unless the issue itself blocks upgrade.
Do not open public GitHub issues for security problems.
Use GitHub's private advisory flow instead:
- affected version or image tag
- deployment shape, for example Docker Compose, reverse proxy, or local-only setup
- exact steps to reproduce
- expected impact
- any logs, screenshots, or proof of concept that help explain the issue
| Severity | Acknowledge | Target remediation window |
|---|---|---|
| Critical | 48 hours | 7 days |
| High | 48 hours | 14 days |
| Medium or Low | 48 hours | 30 days |
These are targets, not hard guarantees. ChannelWatch is still a single-maintainer project.
ChannelWatch is a self-hosted app designed for a trusted home or small-office network. It has meaningful protections, but it is not pretending to be a zero-trust multi-tenant platform.
The safest mental model is:
- one operator-controlled instance
- one writable
/configvolume - private network first
- reverse proxy and TLS if you expose it more broadly
ChannelWatch now supports a setup-first auth model for fresh installs, plus a legacy compatibility path for older installs. The endpoint reference summarizes API key, RBAC session, role, CSRF, and rate-limit behavior in docs/reference/api.md.
Fresh installs should start with secure login. No-auth is an advanced persisted choice, and legacy API-key mode is compatibility-only.
This is a legacy API-key compatibility posture, not the recommended path for new installs.
- one shared API key is stored in
settings.json - the frontend sends it as
X-API-Key - older installs may still rely on it temporarily
Fresh installs should prefer setup-based secure login instead.
RBAC is enabled, but the shared API key still works.
This is better than API-key-only for browser sign-in, but it is not the same as role-only enforcement because the shared key still bypasses role checks.
RBAC is enabled and the shared API-key fallback is not active.
This is the strongest built-in posture available in the current codebase.
This is an explicit advanced mode selected in the setup/UI flow.
- no credentials are required to access the app
- the saved preference is persisted as
auth_mode="none"insettings.json - use it only on a trusted private network
- the Security page provides a way back to secure login by creating the first admin user
- disabling persisted no-auth returns the install to secure-login setup, not to shared browser legacy API-key mode
This disables the normal API-key and RBAC checks.
Use it only on an isolated, fully trusted network. It is intentionally called out in the UI because it opens the app far beyond the recommended posture.
This is a temporary break-glass runtime override, not the recommended way to configure normal long-term auth behavior.
- it changes only the effective runtime auth state for the running process
- it does not rewrite the saved
auth_modeinsettings.json - removing the env override restores the previously saved auth mode on restart
- it does not re-enable the legacy shared browser API-key UX unless that was already part of the saved configuration
Compose and env can start the app and seed an empty config, but the UI and persisted config remain the long-term source of truth for auth setup and normal mode changes.
When RBAC is enabled, ChannelWatch issues a channelwatch_session cookie.
Current cookie behavior:
HttpOnlySecurewhen ChannelWatch sees HTTPS directly or viaX-Forwarded-Proto: httpsSameSite=Strict
For TLS deployments behind a reverse proxy, make sure the proxy forwards X-Forwarded-Proto: https; plain HTTP requests intentionally do not receive the Secure cookie attribute.
Mutating requests made under a logged-in session must also present the matching X-CSRF-Token.
For legacy API-key-authenticated requests, ChannelWatch relies on the custom-header pattern plus a locked-down CORS posture to make browser-driven CSRF much harder.
When CW_DISABLE_AUTH=true, the backend falls back to an Origin-versus-Host check on state-changing requests.
If you leave API-key fallback active, the shared key still grants broad access even when RBAC is turned on. That is why RBAC_WITH_API_KEY_FALLBACK should be treated as a transition mode, not an end state, for exposed deployments.
The backend sends security headers on responses, including:
Content-Security-PolicyX-Content-Type-Options: nosniffX-Frame-Options: SAMEORIGINReferrer-Policy: strict-origin-when-cross-originPermissions-Policywith camera, microphone, and geolocation disabled
ChannelWatch currently encrypts per-DVR API keys stored in dvr_servers[*].api_key using /config/encryption.key.
This helps reduce accidental plaintext exposure on disk, especially when an operator opens settings.json or handles a backup.
It does not solve full host compromise or filesystem exposure.
Important limits:
- the encrypted settings and the encryption key live on the same volume
- someone who can freely read
/configcan usually recover both - other operational secrets in
settings.jsonstill require filesystem protection
So treat this as defense in depth, not as a substitute for host security.
When the UI reads settings from GET /api/settings, sensitive values are masked with **** instead of being echoed back in full.
That helps avoid accidental exposure in the browser, but it is separate from on-disk protection.
The full backup archive is intentionally restorable, not sanitized.
It can include:
settings.jsonchannelwatch.db- per-DVR session state
encryption.keyin a sensitive subfolder
That is why the backup flow is admin-only, and why backup archives should be treated as sensitive material.
If you need to share data for troubleshooting, prefer the debug bundle described in docs/project/PRIVACY.md.
Outbound webhook receivers should still be treated as trusted destinations. The webhook reference documents HMAC signing, retry behavior, receiver verification examples, and receiver URL guidance in docs/reference/webhook.md.
Notification plugins are local Python code loaded into the ChannelWatch process. The plugin loader does narrow the formal contract, but plugins still run inside your own container.
Only install plugins you trust.
ChannelWatch is safest on a private network.
If you expose it beyond that, you should:
- put it behind a reverse proxy
- terminate TLS there
- avoid
CW_DISABLE_AUTH=true - prefer
RBAC_ONLYover shared-key fallback - restrict who can reach the UI at all
The preferred recovery path is the doctor CLI, not manual inspection of files inside the container for shared browser credentials.
Current recovery command:
docker exec -it channelwatch channelwatch doctor reset-admin-password --username <admin>This recovery command is RBAC-only.
- it resets the named admin password
- it invalidates all existing sessions for that user so re-authentication is required
- if you omit
--password, any generated temporary password is printed only to the operator's stdout - the generated password is not written to ChannelWatch app logs or exposed through the UI
If the install is still in true first-run setup, or if auth_mode="none" was explicitly saved, the command exits non-zero and directs the operator to complete secure-login setup from the web UI Security page instead.
The recovery command does not rewrite the persisted auth mode and does not toggle CW_DISABLE_AUTH.
ChannelWatch now supports optional public feeds for calendar and recent activity.
- the ICS calendar feed and the RSS/Atom recent-activity feeds are disabled by default
- access is protected by dedicated feed tokens, not the shared UI API key or session cookie
- treat the full feed URL as a bearer secret because most feed clients send the token in the query string
- the RSS and Atom endpoints share one explicit activity-feed token configuration
The repository now ships a Helm chart, but it is intentionally single replica only.
That is not a temporary documentation omission. The chart itself enforces replicaCount=1 because ChannelWatch uses shared writable state under /config and is not designed for concurrent writers.
The final v0.9 polish pass verified the chart with helm lint, confirmed the default render creates no Ingress, and confirmed --set ingress.enabled=true renders one networking.k8s.io/v1 Ingress. Those checks do not change the single-replica posture.
- generated supervisor credentials at container start instead of public hardcoded credentials
- masked sensitive settings returned by
GET /api/settings - RBAC setup, role checks, session cookies, CSRF checks, and API-key compatibility controls documented in
docs/reference/api.md - browser security headers and a restrictive CSP
- notification image SSRF filtering
- rate limits on API routes, with health and metrics exemptions documented in
docs/reference/api.md
Current release posture:
- images are built and published through GitHub Actions
- provenance attestation is part of the release story
- dependency updates are tracked in the repository
Not yet claimed as complete here:
- cosign image signing
- SBOM publication as a finished shipped requirement
Those remain follow-up hardening work, not a v0.9 claim.
Once a fix is ready, the normal path is:
- publish a patched release
- publish a security advisory with details
- note the fix in
docs/releases/CHANGELOG.md
Please do not disclose the issue publicly before a fix is available, or before 90 days have passed from the initial private report, whichever comes first.
Responsible reporters can be credited in the related advisory if they want that credit.