/** * DC-164 rev4 — operator env overrides → docker `-e KEY=VALUE` flag material. * * WHY NODE + FD PINNING (r3 judge blocker): the contract is validated on a * single file descriptor opened ONCE with O_RDONLY|O_NOFOLLOW; ownership, * mode and regularity are fstat'ed from that descriptor, and the bytes are * read from the SAME descriptor. There is no stat-path-then-reopen window * for a swap (TOCTOU), and O_NOFOLLOW refuses symlinks at open time even if * the target would pass every other check. * * SECURITY CONTRACT (violation => stderr FATAL + exit 3): * - path is a regular file, not a symlink, not a directory/special file * - mode grants neither group nor other access (mode & 0o077 === 0) and * carries no setuid/setgid/sticky bits (mode & 0o7000 === 0) * - ownership matches the reading identity: under euid 0 (the only * supported start.sh runtime) owner must be uid 0; otherwise owner must * equal the euid — you cannot consume another identity's file. * - MISSING file => exit 0, silent: zero overrides is the normal customer * case and needs no contract. * * THREAT MODEL (r3 blocker: "a local root-owned file cannot distinguish the * creator's host from a customer host"): correct — and it does not need to. * This flag is a client-side opt-in rail, NOT the security boundary. The * boundary is cryptographic: a LIFETIME code must HMAC-verify against the * signing secret (.license-secret), which exists ONLY on the creator's * machines. Shipped customer installs carry .license-secret-hash (keyed * digest) which cannot forge or verify an offline lifetime code, and the * online license server never issues lifetime codes to customers. A * customer who creates their own root-owned operator.env flips a flag that * enables a code they can neither forge nor receive. This file therefore * adds no attack surface to the lifetime tier; it removes the need for the * creator to hardcode host exceptions into start.sh. * * TRANSLATION (content is pure data — never sourced/evaled/executed): * - blank lines and lines whose first non-space char is '#' are skipped * - KEY must match ^[A-Za-z_][A-Za-z0-9_]* (optional spaces around it) * - malformed lines: stderr warning, skipped, exit stays 0 * - value = everything after the first '=' verbatim ('#' inside a value * is DATA); surrounding double quotes stripped; leading spaces trimmed * * Usage: node operator-flags.js * Exit: 0 = ok (zero or more KEY=VALUE lines on stdout) * 3 = operator-file contract violated (fail closed: caller forwards * NOTHING and warns; startup may continue without overrides) * 1 = unexpected internal error (fail loud: caller MUST abort) */ 'use strict'; const fs = require('fs'); const path = require('path'); function fatal(msg, code) { process.stderr.write(msg + '\n'); process.exit(code); } function main() { const file = process.argv[2]; if (!file || typeof file !== 'string') fatal('operator-flags: missing argument', 3); let fd; // Advisory pre-check: reject anything that is not a plain regular file // BEFORE opening it, so no open() can block on a FIFO/device (a block // device may still block even with O_NONBLOCK — POSIX leaves its effect // undefined there). The AUTHORITATIVE checks run on the pinned fd below; // a swap between lstat and open is caught by O_NOFOLLOW + fstat on the // SAME descriptor (TOCTOU r3 blocker remains closed). try { const lst = fs.lstatSync(file); if (!lst.isFile()) { fatal(`operator-flags: FATAL — ${file} is not a regular file (pre-check: ${lst.isSymbolicLink() ? 'symlink' : lst.isFIFO() ? 'FIFO' : lst.isDirectory() ? 'directory' : lst.isCharacterDevice() || lst.isBlockDevice() ? 'device' : 'special'})`, 3); } } catch (e) { if (e.code === 'ENOENT') process.exit(0); // absent = zero overrides if (e.code === 'EACCES' || e.code === 'EPERM' || e.code === 'ENOTDIR') { fatal(`operator-flags: FATAL — cannot stat ${file}: ${e.code}`, 3); } throw e; } try { // O_NONBLOCK completes r5 blocker: opening a FIFO WITHOUT it blocks the // process indefinitely at open() (startup DoS). With O_RDONLY|O_NONBLOCK // a FIFO opens instantly and is rejected below by the isFile() check; // for regular files O_NONBLOCK is a no-op (POSIX: undefined effect, // universally ignored on Linux/macOS regular-file reads). fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK); } catch (e) { if (e.code === 'ENOENT') process.exit(0); // absent = zero overrides if (e.code === 'ELOOP') fatal(`operator-flags: FATAL — ${file} is a symlink; refusing (O_NOFOLLOW)`, 3); if (e.code === 'ENOTDIR') fatal(`operator-flags: FATAL — path component not a directory: ${file}`, 3); if (e.code === 'EACCES' || e.code === 'EPERM') fatal(`operator-flags: FATAL — cannot open ${file}: ${e.code}`, 3); throw e; // unexpected => exit 1 (loud) } try { const st = fs.fstatSync(fd); // metadata OF THE PINNED FD if (!st.isFile()) fatal(`operator-flags: FATAL — ${file} is not a regular file`, 3); if ((st.mode & 0o077) !== 0 || (st.mode & 0o7000) !== 0) { fatal(`operator-flags: FATAL — ${file} mode ${(st.mode & 0o7777).toString(8)} grants group/other access or special bits (contract: e.g. 0600)`, 3); } const euid = typeof process.geteuid === 'function' ? process.geteuid() : -1; if (euid === 0) { if (st.uid !== 0) fatal(`operator-flags: FATAL — ${file} owned by uid ${st.uid} (root requires uid 0)`, 3); } else if (st.uid !== euid) { fatal(`operator-flags: FATAL — ${file} owned by uid ${st.uid}, not the invoking uid ${euid}`, 3); } const text = fs.readFileSync(fd, 'utf8'); // read the SAME object const out = []; let lineno = 0; for (const raw of text.split('\n')) { lineno++; const line = raw.replace(/^\s+/, ''); if (line === '' || line.startsWith('#')) continue; const m = /^([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$/.exec(line); if (!m) { process.stderr.write(`operator-flags: skipping line ${lineno} (not KEY=VALUE): ${line.slice(0, 60)}\n`); continue; } let value = m[2].replace(/^\s+/, ''); if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1); out.push(`${m[1]}=${value}`); } if (out.length) process.stdout.write(out.join('\n') + '\n'); process.exit(0); } finally { try { fs.closeSync(fd); } catch (_) { /* exiting anyway */ } } } main();