Commit Graph
13 Commits
Author SHA1 Message Date
Krystie 20d280f1dd DC-033: fix getLocalVersion __dirname resolution
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled
The SelfUpdater's getLocalVersion() used __dirname to find package.json
and VERSION, but server.js loads the module via './src/docker/self-updater'
so __dirname resolves to /app/src/docker inside the container — which
has no package.json. Result: /api/v1/system/version silently returned
{version: '0.0.0', commit: null} and checkForUpdate() always thought we
were outdated.

Walk a candidate list of paths (api root first, __dirname second) so the
function works regardless of where the module is required from. Log to
stderr on total failure instead of swallowing silently.

Verified on DNS2: /api/v1/system/version now returns
{"name":"DashCaddy","version":"1.14.8","commit":"fef7e07"}
(v1.14.8 with the security fixes DC-020..032).
2026-07-05 21:49:39 -07:00
Krystie ba23cdff02 DC-032: fix health checker authLimiter feedback loop + ca.sami DNS
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled
Three coordinated changes to stop every gated *.sami service flipping
red after ~20 probes:

1. health-checker.js _doRequest() now sends X-DashCaddy-HealthCheck: 1
   on every outgoing probe. Caddy uses this header (combined with a
   trusted source IP via the new @healthcheckProbe matcher in the
   dashcaddy_auth snippet) to bypass forward_auth for local container
   probes. Without the bypass, forward_auth 401's every probe, and the
   authLimiter (20 req / 15 min, DC-027) caps us out within minutes.

2. evaluateHealth() default expectedStatusCodes now includes 401, 403,
   and 429. Defense in depth — if a future Caddy reload drops the
   bypass, 429 from the rate-limited gate no longer marks the service
   as down (it just means the gate answered, which proves the service
   is reachable through Caddy).

3. (start.sh — already shipped on the running container, will land
   with the next release build) ca.sami now maps to 100.121.150.22
   (DNS2) instead of 127.0.0.1, which is the container's own loopback
   where nothing serves :443. The CA web UI lives on DNS2's Caddy.

Tests:
- evaluateHealth: 401, 403, 429 accepted by default
- _doRequest: X-DashCaddy-HealthCheck: 1 always present
- _doRequest: user-supplied headers preserved alongside marker

Bump 1.14.7 → 1.14.8.
2026-07-05 12:58:13 -07:00
Krystie 95f558c49f DC-030: bake /etc/hosts overrides into start.sh (fix git.sami resolution in container)
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled
The container's health-checker runs against Caddy via /etc/hosts resolution.
The node:20-alpine base image has no entries for *.sami, so without explicit
--add-host flags every *.sami probe resolves via the configured DNS server
(100.121.150.22 Technitium or 8.8.8.8) — both of which DO resolve *.sami but
return the WAN/Tailscale IP. That works for most services because Caddy on
DNS2:443 handles them.

BUT: a previous container run passed --add-host=git.sami:100.81.59.99
(DNS3's Tailscale IP). DNS3 does NOT serve HTTPS on 443 — Gitea listens on
:3030 only. So git.sami health checks inside the container hit DNS3:443,
get ECONNREFUSED, and the dashboard shows git.sami as down even though Caddy
on DNS2:443 correctly routes git.sami → 100.81.59.99:3030.

Fix: inject the correct --add-host flags from start.sh (the source of truth
for container setup) so future recreates get consistent resolution. git.sami
is intentionally left OUT — Caddy on DNS2:443 is the only correct ingress
for git.sami traffic.

Also documents the rationale so the next person doesn't reintroduce the
git.sami override by accident.

Live verified:
- container /etc/hosts has all needed entries except git.sami
- curl https://git.sami/ from inside container → 200 (via Caddy on :443)
- curl https://sync.sami/ from inside container → 302 (upstream redirect)
- curl https://router.sami/ from inside container → 302 (upstream redirect)
2026-07-03 00:08:55 -07:00
Krystie a92eeceae5 DC-029: skip authLimiter for already-authenticated requests
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled
The DC-027 rate limiter on /api/v1/auth/* shipped with skip: () => isTest,
which counted every request — including those from a logged-in TOTP session.
Caddy's forward_auth fires /auth/gate/* on every page-load asset (HTML, JS,
CSS, XHR), so a normal browser session exhausted the 20-req/15-min budget
within ~3 page loads and started getting 429 'Too many auth requests' even
with a valid session cookie.

Fix: extend skip to also return true when req.auth.type is 'session',
'jwt', or 'apikey' (set by jwtApiKeyAuthMiddleware, which runs upstream
of the limiter). The unauthenticated path is still rate-limited — DC-027's
credential-scraping defense is preserved.

Also closes the uncommitted working-tree changes for:
- DC-026: routes/auth/sso-gate.js — pre-auth check in buildLoginPage,
  redirected error fallbacks to status.sami?auth=required&return=...
- DC-022: dashcaddy-api/VERSION bumped to fef7e07
- status/index.html + status/js/tailscale-devices.js — Tailscale device card

4 new regression tests pin the fix:
- skips when req.auth.type === 'session'
- skips when req.auth.type === 'jwt'
- skips when req.auth.type === 'apikey'
- still counts UNAUTHENTICATED requests (defense preserved)

Live verified: 50/50 authenticated /auth/gate/plex calls passed (was
20/30 before fix). plex.sami/dashcaddy-login returns 200 with no redirect
loop. Plex auto-login token round-trips end-to-end.
2026-07-02 18:27:03 -07:00
Krystie 57de3cb8e3 chore(release): bump to 1.14.7
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled
2026-07-01 12:19:03 -07:00
Krystie fef7e07b49 DC-026/027/028: close 3 more auth security holes + rate limit /auth/* + audit credential exposures
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled
[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).
2026-07-01 04:20:57 -07:00
Krystie bfa4ba570e DC-025: harden updater — channel gate + safe locked-file replacement
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled
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.
2026-07-01 04:02:30 -07:00
Krystie b7624cc507 DC-024: Bump installer version to 1.14.6 and sync VERSION to current commit
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled
- dashcaddy-installer/install.sh: 1.1.0 → 1.14.6 (matches current release)
- dashcaddy-api/VERSION: 10f72afa5f51e4 (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).
2026-07-01 03:50:00 -07:00
Krystie a5f51e4a0c DC-023: operational fixes — DNS, rate limiter, version sync
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled
- 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)
2026-07-01 03:10:53 -07:00
Krystie e73bfbb0a1 DC-021: build pipeline now ships src/ + hygiene for generated artifacts
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)
2026-07-01 03:09:59 -07:00
Krystie 2439ed3e85 DC-022: close 3 TOTP auth security holes
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]
2026-07-01 03:09:33 -07:00
Krystie 69be51b8aa chore(release): bump to 1.14.6 — patched source
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled
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.
2026-07-01 00:55:13 -07:00
Krystie 10f72af959 fix(update): proper require path fixes + license-keygen restore for v1.14.4 compatibility
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled
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.
2026-07-01 00:32:23 -07:00