refactor: extract isLoopbackBind to lib/net.mjs (#125) (#127)

Follow-up cleanup from #115's review. isLoopbackBind was defined in server.mjs and
copy-pasted into test-features.mjs (with a "keep in sync" comment, because importing
server.mjs would run server.listen()). Extracted to a new importable lib/net.mjs;
server.mjs and the test now import the one definition, removing the drift surface.

Pure refactor — the function body is byte-identical to the prior server.mjs version,
the TUI LAN-gate call site is unchanged, and the 8 isLoopbackBind truth-table tests now
exercise the real shared function. 181 tests pass.

ALIGNMENT.md: touches server.mjs but adds/removes no operation — it relocates an existing
(#115) helper into a module. cli.js citation N/A under Rule 2.

Independent fresh-context reviewer (opus): APPROVE (Iron Rule 10) — byte-identical body,
exactly one definition, wiring + scope correct, no test dropped, scope clean.

Closes #125.

Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
dtzp555-max
2026-06-01 07:03:55 +10:00
committed by GitHub
co-authored by taodeng Claude Opus 4.8
parent 0000926358
commit 1b02f181fa
3 changed files with 13 additions and 17 deletions
+9
View File
@@ -0,0 +1,9 @@
// OCP network helpers — shared so server.mjs and tests use one definition. (issue #125)
// A bind address is "loopback" only if it cannot be reached from another host.
// Any other address (0.0.0.0, ::, a concrete LAN/Tailscale IP, etc.) is
// network-exposed and must trigger the TUI LAN gate.
export function isLoopbackBind(addr) {
return addr === "127.0.0.1" || addr === "::1" || addr === "localhost" ||
addr === "::ffff:127.0.0.1" || /^127\./.test(addr);
}
+1 -8
View File
@@ -36,6 +36,7 @@ import { dirname, join } from "node:path";
import { homedir } from "node:os"; import { homedir } from "node:os";
import { validateKey, recordUsage, getUsageByKey, getUsageTimeline, getRecentUsage, createKey, listKeys, revokeKey, closeDb, checkQuota, updateKeyQuota, getKeyQuota, findKey, cacheHash, getCachedResponse, setCachedResponse, clearCache, getCacheStats, hasCacheControl, singleflight, getInflightStats } from "./keys.mjs"; import { validateKey, recordUsage, getUsageByKey, getUsageTimeline, getRecentUsage, createKey, listKeys, revokeKey, closeDb, checkQuota, updateKeyQuota, getKeyQuota, findKey, cacheHash, getCachedResponse, setCachedResponse, clearCache, getCacheStats, hasCacheControl, singleflight, getInflightStats } from "./keys.mjs";
import { DEFAULT_PORT } from "./lib/constants.mjs"; import { DEFAULT_PORT } from "./lib/constants.mjs";
import { isLoopbackBind } from "./lib/net.mjs";
import { runTuiTurn, reapStaleTuiSessions } from "./lib/tui/session.mjs"; import { runTuiTurn, reapStaleTuiSessions } from "./lib/tui/session.mjs";
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -299,14 +300,6 @@ const TUI_CWD = process.env.OCP_TUI_CWD || `${process.env.HOME}/.ocp-tui/work`
const TUI_HOME = process.env.OCP_TUI_HOME || process.env.HOME; const TUI_HOME = process.env.OCP_TUI_HOME || process.env.HOME;
const TUI_ENTRYPOINT = process.env.OCP_TUI_ENTRYPOINT || "cli"; // cli|auto|off — see ADR 0007 const TUI_ENTRYPOINT = process.env.OCP_TUI_ENTRYPOINT || "cli"; // cli|auto|off — see ADR 0007
// A bind address is "loopback" only if it cannot be reached from another host.
// Any other address (0.0.0.0, ::, a concrete LAN/Tailscale IP, etc.) is
// network-exposed and must trigger the TUI LAN gate. (issue #115)
function isLoopbackBind(addr) {
return addr === "127.0.0.1" || addr === "::1" || addr === "localhost" ||
addr === "::ffff:127.0.0.1" || /^127\./.test(addr);
}
// SECURITY fail-loud: TUI-mode is incompatible with any configuration that allows // SECURITY fail-loud: TUI-mode is incompatible with any configuration that allows
// non-operator prompts to reach the interactive claude session. Three cases: // non-operator prompts to reach the interactive claude session. Three cases:
// 1. AUTH_MODE=multi — guest/anonymous keys can submit prompts. // 1. AUTH_MODE=multi — guest/anonymous keys can submit prompts.
+3 -9
View File
@@ -4,6 +4,7 @@
* Tests database layer functions directly — no server needed. * Tests database layer functions directly — no server needed.
*/ */
import { getDb, createKey, listKeys, validateKey, recordUsage, checkQuota, updateKeyQuota, getKeyQuota, findKey, cacheHash, getCachedResponse, setCachedResponse, clearCache, getCacheStats, closeDb, hasCacheControl, singleflight, getInflightStats } from "./keys.mjs"; import { getDb, createKey, listKeys, validateKey, recordUsage, checkQuota, updateKeyQuota, getKeyQuota, findKey, cacheHash, getCachedResponse, setCachedResponse, clearCache, getCacheStats, closeDb, hasCacheControl, singleflight, getInflightStats } from "./keys.mjs";
import { isLoopbackBind } from "./lib/net.mjs";
import { createHash } from "node:crypto"; import { createHash } from "node:crypto";
import { strict as assert } from "node:assert"; import { strict as assert } from "node:assert";
import { unlinkSync } from "node:fs"; import { unlinkSync } from "node:fs";
@@ -1798,17 +1799,10 @@ test("KEY_NAME_RE: 65-char string → invalid", () => {
assert.ok(!KEY_NAME_RE.test("x".repeat(65))); assert.ok(!KEY_NAME_RE.test("x".repeat(65)));
}); });
// ── isLoopbackBind helper (issue #115) ────────────────────────────────────── // ── isLoopbackBind helper (issue #115, extracted to lib/net.mjs via #125) ──────
// MIRRORS server.mjs isLoopbackBind — copied verbatim to avoid importing server.mjs // Tests the imported lib/net.mjs helper — the real shared definition used by server.mjs.
// (top-level server.listen() would start a live HTTP server).
// Keep in sync with the definition in server.mjs above the TUI gate block.
console.log("\nisLoopbackBind helper (issue #115):"); console.log("\nisLoopbackBind helper (issue #115):");
function isLoopbackBind(addr) {
return addr === "127.0.0.1" || addr === "::1" || addr === "localhost" ||
addr === "::ffff:127.0.0.1" || /^127\./.test(addr);
}
test("isLoopbackBind: '127.0.0.1' → true", () => { test("isLoopbackBind: '127.0.0.1' → true", () => {
assert.equal(isLoopbackBind("127.0.0.1"), true); assert.equal(isLoopbackBind("127.0.0.1"), true);
}); });