[pending-judge] feat(DC-164 rev5): operator env overrides — fd-pinned contract, no-blocking opens, fail-loud wiring, untracked license material
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.
This commit is contained in:
7 files changed
+382
-144
No files matched your search
+6
-1
@@ -26,4 +26,9 @@ updates/
|
||||
# Once v1.15.0 ships src/ properly, these become obsolete.
|
||||
dashcaddy-api/*.js
|
||||
!dashcaddy-api/license-keygen.js
|
||||
!dashcaddy-api/platform-paths.js
|
||||
!dashcaddy-api/platform-paths.js
|
||||
# DC-164: runtime license material must NEVER be repo-tracked — a deploy
|
||||
# checkout overwriting the live signing secret revokes activations (incident 2026-10-08).
|
||||
dashcaddy-api/.license-secret
|
||||
dashcaddy-api/.license-counter
|
||||
dashcaddy-api/data/services.json
|
||||
Reference in new issue
Block a user