From feb13001273b64a133f29ea36dc19950f0c824c7 Mon Sep 17 00:00:00 2001 From: dormouse-bot <287024035+dormouse-bot@users.noreply.github.com> Date: Thu, 1 Oct 2026 12:29:29 +0000 Subject: [PATCH] Let loopback-lint see a positional bind whose port is a call The node positional form scanned the port with [^,)]+, which stopped at the inner ) of Number(process.env.PORT || 0), so hosted/server/dev.ts's loopback bind went unlisted. The port may now wrap one call, and a selftest fixture pins that shape. Refs #873 --- scripts/loopback-lint-selftest.mjs | 3 +++ scripts/loopback-lint.mjs | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/loopback-lint-selftest.mjs b/scripts/loopback-lint-selftest.mjs index b5bf21061..7d6a5ec98 100644 --- a/scripts/loopback-lint-selftest.mjs +++ b/scripts/loopback-lint-selftest.mjs @@ -56,6 +56,9 @@ const EXT_TARGET = 'vscode-ext/vitest.smoketest.config.mts'; */ const FIXTURES = [ ['node, positional', "\nexport function __selftest(s) { s.listen(9999, '127.0.0.1'); }\n"], + // A port computed by a call — `Number(process.env.PORT || 0)` — whose inner + // `)` ends a scan that stops at the first one. + ['node, positional', "\nexport function __selftest(s) { s.listen(Number(process.env.PORT || 0), '127.0.0.1'); }\n"], ['node, options object', "\nexport function __selftest(s) { s.listen({ port: 9999, host: '127.0.0.1' }); }\n"], ['@hono/node-server', "\nexport function __selftest(app) { serve({ fetch: app.fetch, port: 9999, hostname: '127.0.0.1' }); }\n"], ['ws, explicit loopback host', "\nexport function __selftest() { return new WebSocketServer({ host: '127.0.0.1' }); }\n"], diff --git a/scripts/loopback-lint.mjs b/scripts/loopback-lint.mjs index 8a8f4268c..89864f55b 100644 --- a/scripts/loopback-lint.mjs +++ b/scripts/loopback-lint.mjs @@ -36,6 +36,8 @@ * options nest again (`headers`, `cookieDomainRewrite`), and `configure` * takes a function whose body carries braces of its own. A `host` written * below one of those is a miss, and the audit is what covers it. + * - A positional port may wrap one call (`Number(process.env.PORT || 0)`); + * a port expression nesting parentheses deeper hides the bind. * - Outside `ws`, it matches only an explicit loopback host. A listener that * binds every interface (`.listen(port)` with no host) is a different and * larger problem, and `relay/` does it deliberately from config, so @@ -127,7 +129,7 @@ const NESTED_KEYS = '(?:[^{}]|\\{(?:[^{}]|\\{[^{}]*\\})*\\})*?'; * exercises is a claim, not a check. */ const BIND_FORMS = [ - { label: 'node, positional', re: `\\.listen\\(\\s*[^,)]+,\\s*${LOOPBACK}` }, + { label: 'node, positional', re: `\\.listen\\(\\s*(?:[^,()]|\\([^()]*\\))+,\\s*${LOOPBACK}` }, { label: 'node, options object', re: `\\.listen\\(\\s*\\{[^}]*?host\\s*:\\s*${LOOPBACK}` }, { label: '@hono/node-server', re: `\\bserve\\(\\s*\\{[^}]*?hostname\\s*:\\s*${LOOPBACK}` }, { label: 'ws, explicit loopback host', re: `${WS_NEW}host\\s*:\\s*${LOOPBACK}` },