[DC-026] routes/auth/sso-gate.js — fix sessionDuration='never' bypass
Both /auth/gate/:serviceId and /auth/app-token/:serviceId had a session
check gated on `sessionDuration !== 'never'`. An admin setting TOTP to
never-expire accidentally created an authentication-free path to credential
injection (Basic Auth, X-Api-Key, Plex/Prowlarr tokens). Patched: session
required whenever TOTP is enabled, period. Added 8 regression tests.
[DC-027] src/utilities/middleware.js — rate limit /auth/*
New authLimiter (20 req / 15 min) on /auth/keys, /auth/jwt, /auth/gate,
/auth/app-token. These endpoints expose credentials and were unmetered.
Without this, an attacker with a guessed session cookie could burn through
every credential-touching endpoint. Added 5 tests.
[DC-028] src/security/audit-logger.js — log credential exposures
/auth/gate and /auth/app-token were in SKIP_PATHS, silently dropping
every credential-exposure event from the audit log. Combined with the
GET-skip rule, NONE of these events were being recorded. Now logged
with named actions: auth.credential-injection, auth.app-token-issue,
auth.api-key-generate, auth.api-key-revoke, auth.jwt-mint. Added 9 tests.
[start.sh] Disable in-container self-updater
DASHCADDY_UPDATE_ENABLED=false. Without this, the container kept writing
trigger.json every 30 min and clobbered my in-progress host edits. The
path unit on the host is still active for manual triggers, but the
container won't auto-update itself — only when an admin clicks the
update button or a new release is manually published.
[package.json] Bump to 1.14.7
Test results: 1066/1066 passing across 39 suites (added 22 new tests).
The host-side updater has been silently broken in two ways:
1. Empty staging directories would cause rm -rf of live routes/src with no
replacement, leaving the host tree gutted while the container kept serving
from its own image. Now deploy_tree() refuses to delete unless the staging
source has actual files.
2. chattr +i on critical files (used to protect security-hotfixed routes from
being clobbered by upstream tarballs) caused rm -rf to partially execute
then fail under set -e, leaving the host in a half-deleted state. Now
deploy_tree() scans for immutable files, unlocks them before replace,
and re-locks them after — so security-locked files survive every update.
Also adds:
- Channel gate: trigger.json channel=prerelease/beta/rc/alpha is rejected
unless ALLOW_PRERELEASE=true is set in /opt/dashcaddy/updates/channel.conf.
Default is 'stable only', safe for production. Staging hosts opt in.
- channel.conf.example documenting the new opt-in mechanism.
Verified end-to-end: manual trigger.json → path unit fired → routes (53 files)
+ src (62 files) deployed → container rebuilt → health check passed. totp.js
remained locked with security edits intact.
- dashcaddy-installer/install.sh: 1.1.0 → 1.14.6 (matches current release)
- dashcaddy-api/VERSION: 10f72af → a5f51e4 (current HEAD with TOTP security fixes)
The host source tree was rebuilt from the published v1.14.6 tarball to fix a
deletion gap where /opt/dashcaddy/dashcaddy-api/{routes,src}/ were gutted by an
interrupted prior update cycle. Container was unaffected (built from image).
- VERSION: bump from 1.14.4 to 1.14.6 to match package.json (HEAD had stale value)
- middleware.js: apply existing totpLimiter (10/15min) to /totp/setup endpoint
(was previously unmetered, allowing secret enumeration)
- dashcaddy-update.sh: hook post-deploy-patches.sh into the update flow
so the container can survive transitions between broken → fixed tarballs
- start.sh: add --add-host flags for get.dashcaddy.net and get2.dashcaddy.net
so the container can resolve the release server (was failing with ENOTFOUND)
The release tarball previously omitted dashcaddy-api/src/, which meant the
in-container self-updater had to apply post-deploy patches (dashcaddy-post-
deploy-patches.sh) to work around missing files. That script generates 37
flat copies of src/ files at the dashcaddy-api/ root level to satisfy
broken require() paths. With proper src/ shipping, those files become
obsolete, but they were still being shown as untracked in git.
Changes:
- BUILD-PIPELINE-FIX.md documents the build pipeline fix (in /opt/dashcaddy-release/
build-release.sh — sibling repo, not tracked here)
- .gitignore now ignores the 37 generated post-deploy artifacts plus the
backups/ and updates/ runtime directories, so 'git status' stays clean
- scripts/dashcaddy-post-deploy-patches.sh is now tracked so it's preserved
across rebuilds (still useful as a safety net for transitional installs)
1. /totp/recovery-info: was PUBLIC, leaking TOTP configuration status
to unauthenticated attackers. Now requires valid session (401 otherwise).
2. /totp/check-session: had an unconditional bypass that returned
authenticated:true whenever totpConfig.enabled was false. This let
anyone reach authenticated endpoints without credentials.
Now throws AuthenticationError instead.
3. /totp/setup: was unmetered despite generating secrets. Added 3/hour
per-IP rate limit in addition to the existing global 10/15min limiter.
All changes verified live via https://status.sami:
- recovery-info unauth → 401 [DC-110] (was 200)
- check-session no cookie → 401 TOTP protection required (was 200)
- 4th setup attempt → 429 [DC-429]
Following DC-021 (commit 10f72af) which restored working require paths and
license-keygen.js, this commit bumps the version metadata so the next
release build publishes v1.14.6 instead of re-tagging v1.14.4.
The source is functionally v1.14.4 + fixes; the version bump tells the
updater we're ahead of upstream's broken v1.14.4.
v1.14.4 (commit d2a48b1) shipped with broken relative paths and missing
license-keygen.js module. This commit:
- server.js: 26 '../src/...' requires rewritten to './src/...' (server is
at API root, must use ./src for files in src/)
- src/managers/license-manager.js: './license-keygen' rewritten to
'../../license-keygen' (license-keygen.js lives at API root)
- src/docker/self-updater.js: './platform-paths' rewritten to
'../../platform-paths' (platform-paths.js lives at API root)
- license-keygen.js: restored to root (was missing from v1.14.4 tarball)
- VERSION: bumped to d2a48b1-patched (matches upstream commit but with
our fixes baked in)
Makes the v1.14.4 source buildable and runnable without external patches.
Companion to scripts/dashcaddy-post-deploy-patches.sh which applies these
fixes automatically during the host-side update flow.