Feature: /etc/dashcaddy/operator.env (root-owned KEY=VALUE file) is
forwarded into the DashCaddy container as docker -e flags by start.sh.
Canonical flag: ALLOW_LIFETIME_LICENSE=true on the CREATOR host only.
This is a client-side rail, NOT the security boundary: lifetime codes
must HMAC-verify against the creator-only signing secret, which is never
shipped to customers (installs carry only .license-secret-hash; the
license server never issues lifetime codes). Flipping the flag on a
customer host grants nothing. Missing file = normal case (zero flags).
Design (per judge blockers):
1. scripts/operator-flags.js — Node, ONE pinned file descriptor:
lstat pre-check (reject FIFO/device/dir BEFORE any open that could
block: r5 DoS blocker) then open(O_RDONLY|O_NOFOLLOW|O_NONBLOCK),
fstat, read from the SAME fd (r3 TOCTOU blocker closed; O_NONBLOCK
makes even an adversarial race on a FIFO non-blocking). Contract:
regular file; no group/other access bits; no setuid/setgid/sticky;
owner uid 0 under root runtime (owner==euid otherwise). Symlink
refused at open even when the target would pass all checks.
Violation => stderr FATAL + exit 3. Content is pure data: never
sourced/evaled/executed; $(...) values stay inert text (tested).
2. start.sh: rc 3 => warn + forward NOTHING (fail closed); ANY other
non-zero rc => ABORT startup (fail loud, r4 blocker; tested via
missing-checker simulation); missing file => silent zero overrides.
Single helper invocation; keys-only audit line at boot (no values).
3. Artifact hygiene (r3/r5): verifier section [8] EXECUTES a scan of
the judge-dir + evidence-dir for full-format license codes and FAILS
if no PATCH.diff is present at all (no vacuous pass); indexed
0_/1_/2_/3_ names recognized. (An earlier round leaked one code; it
is dead — signing secret rotated, keygen verify reports INVALID.)
Root-cause hardening (2026-10-08 incident: deploy checkouts overwriting
git-TRACKED runtime state revoked the owner's premium and hid service
cards):
- untrack dashcaddy-api/data/services.json AND add an explicit
.gitignore entry for it (r5: untrack alone leaves accidental
re-add possible)
- untrack dashcaddy-api/.license-secret + .license-counter, extend
.gitignore. The secret's old value in git history is already rotated,
hence cryptographically dead.
Tests: 17 executed operator-flags tests — incl. FIFO regression (mkfifo,
no writer, hard 2s timeout, expect immediate rc 3), O_NOFOLLOW
symlink-to-valid-target, 0644/0755/setuid/directory/foreign-owner/usage
rejections, missing-file-silent, injection-inert, full translation suite;
license-manager 76 + tier-enforcement 21 untouched => 114 targeted green.
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)
Working tree accumulated 172 untracked/modified files from the auto-updater:
- 19 secret/runtime files in dashcaddy-api/data/ that should never be tracked
- 199 byte-identical duplicates of tracked files dumped at root by an
outdated rsync/cp step
- 6 scratch debug scripts (cm_check.js, login_test.js, full_test.js, ...)
- 7 .bak-* files from start.sh and dashcaddy-update.sh rollback branches
- Root-level routes/ directory: dead code, container COPYs dashcaddy-api/routes/
.gitignore now ignores:
- dashcaddy-api/data/ (runtime: credentials, secrets, history)
- start.sh.bak*, scripts/*.bak* (auto-updater rollback backups)
- updates/ (auto-updater runtime state)
- cm_check*.js, *_test.js (scratch debug scripts)
Removed dead code:
- routes/openclaw.js (replaced by dashcaddy-api/routes/openclaw.js)
Recreated runtime scripts that were deleted with their duplicates:
- start.sh (canonical container-start, 47-line full config)
- scripts/dashcaddy-update.sh was already untracked; fixed the tracked
dashcaddy-api/scripts/dashcaddy-update.sh instead (see next commit)
Net change: 172 → 17 files in working tree.
- Add missing fetchT dep to session-handlers.js (fixes ctx is not
defined error that broke jellyfin/emby/plex/syncthing SSO)
- Replace all ctx.fetchT calls with direct fetchT usage
- Remove server-old.js (69K monolith backup) from tracking
- Remove AI-generated doc artifacts from repo root
- Update .gitignore to prevent re-adding these files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Full codebase including API server (32 modules + routes), dashboard frontend,
DashCA certificate distribution, installer script, and deployment skills.