mirror of
https://github.com/dtzp555-max/ocp.git
synced 2026-07-19 09:44:07 +00:00
eeec2bf83d
* fix(setup): never carry test-only key-store redirection vars into a server OCP launches (A4) Defense-in-depth for the key-store isolation shipped in #163, plus a correction to the overstated claim that fix's comments made. Surfaced by an independent (Codex) re-review. Background: keys.mjs honors OCP_DIR_OVERRIDE only when NODE_ENV === "test", so the key store can be pointed at a scratch dir for the test suite. If BOTH vars reached a production daemon's environment, it would open a scratch/empty key store instead of ~/.ocp/ocp.db — in AUTH_MODE=multi a silent total auth outage. #163's comments claimed a production server "runs without NODE_ENV, so it CANNOT honor the override no matter how the variable got in." That is not something keys.mjs can enforce — it is only true while the daemon's env happens to lack NODE_ENV=test. This PR makes it true for every server OCP itself launches, and softens the docs to stop overclaiming. Three parts (all in OCP's own launch/installer paths — no server.mjs change, no cli.js analogue): 1. scripts/lib/plist-merge.mjs — new exported NEVER_PRESERVE = {NODE_ENV, OCP_DIR_OVERRIDE}, stripped from the preserved set in BOTH mergePlistEnv and mergeSystemdEnv. The preservation rule ("keys only in the EXISTING unit are kept verbatim") was the vector: a unit that once carried these test-only vars would otherwise survive every setup re-run. setup.mjs's template never injects them, so preservation was the only entry path, and this closes it. 2. ocp (cmd_restart manual fallback) — the one direct `node server.mjs` launch OCP controls now runs under `env -u NODE_ENV -u OCP_DIR_OVERRIDE`, so a maintainer who exported both while debugging and then restarted can't silently boot the daemon onto a scratch store. 3. keys.mjs + test-env.mjs — softened the overstated comments to state what is actually enforced (the two-key gate makes neither var alone do anything; OCP's launchers strip both) and to name the one residual path honestly: a hand-rolled `node server.mjs` with both vars explicitly exported, bypassing every launcher — for which the loud getDb() "NOT the default" log is the backstop. No library-level gate can catch an operator who both sets a test flag and bypasses the launchers; the honest fix is a non-silent wrong-store, which #163 already provides. Severity: LOW (defense-in-depth; the default/shipped path was already safe). No behavior change on any correctly-configured install. ALIGNMENT.md: this PR does not touch server.mjs, so the cli.js-citation hard requirement does not apply; and no cli.js operation is involved — key-store isolation and installer env hygiene are entirely OCP-owned (no Class A / cli.js-mirror surface). Tests: +4 mutation-proven (3 behavioral: drop the `!NEVER_PRESERVE.has(k)` guard in either merge fn and they fail — verified 326 passed / 3 failed under mutation; restored). The `ocp` bash `env -u` line is verified by `bash -n` + inspection (the suite does not exec the installer/daemon). Full suite: 329 passed / 0 failed (was 325). Version bump + CHANGELOG deferred to the later chore(release) PR, per the repo's #148/#149/#150 -> #151 convention (matching PR #164). Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com> * test(setup): assert NEVER_PRESERVE.size === 2 so the "exactly two" test matches its name Reviewer nit (LOW): the membership assertion let a future spurious third entry slip past a test whose name promises "exactly the two". Behavior stays guarded by the 3 mutation-proof tests; this just makes the contract test honest. Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com> --------- Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude <claude-opus-4-8> <noreply@anthropic.com>
27 lines
1.6 KiB
JavaScript
27 lines
1.6 KiB
JavaScript
// Imported FIRST by test-features.mjs, before keys.mjs, so this runs before anything can open
|
|
// the key store. ESM hoists imports and evaluates them in order, so a `process.env.X = ...`
|
|
// statement in the test's own body would run too late — hence a separate module.
|
|
//
|
|
// Why this exists: `npm test` used to write real, UNREVOKED api_keys rows into the operator's
|
|
// live ~/.ocp/ocp.db (the same database the running server reads) — two per run, unbounded.
|
|
// It also made the suite racy: two concurrent runs (e.g. review worktrees) shared one file, so
|
|
// `listKeys()` could miss "test-user-1" and the `in` check would throw on undefined.
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
export const TEST_OCP_DIR = mkdtempSync(join(tmpdir(), "ocp-test-"));
|
|
|
|
// BOTH are required. keys.mjs honors OCP_DIR_OVERRIDE only when NODE_ENV === "test", so neither
|
|
// var alone redirects anything — a stray OCP_DIR_OVERRIDE in a production env is inert without
|
|
// NODE_ENV=test alongside it. (A daemon OCP launches never carries either: the service units and
|
|
// the `ocp` restart fallback strip both — see plist-merge NEVER_PRESERVE / keys.mjs's comment.)
|
|
process.env.NODE_ENV = "test";
|
|
process.env.OCP_DIR_OVERRIDE = TEST_OCP_DIR;
|
|
|
|
// Remove the scratch store on exit. Without this the fix would trade unbounded growth in
|
|
// ~/.ocp/ocp.db for unbounded growth in $TMPDIR — better, but still litter.
|
|
process.on("exit", () => {
|
|
try { rmSync(TEST_OCP_DIR, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
});
|