From 6cd1ac9a761bc3f97ced67cfcdaf7fdcda49ad5d Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sat, 25 Jul 2026 00:51:18 -0700 Subject: [PATCH 1/4] fix(deps): resolve postcss + tar audit findings via overrides npm audit flagged postcss <=8.5.17 (path traversal in previous-sourcemap auto-loading, GHSA-r28c-9q8g-f849, high) and tar <=7.5.20 (uncontrolled recursion DoS via crafted long-path tar, GHSA-r292-9mhp-454m, moderate), both transitive. Pin both via overrides, matching this file's existing pattern for other transitive-dep security fixes: bump the existing tar override past its first-patched version (7.5.19 -> 7.5.21) and add a new postcss override (8.5.18, first patched version). Closes task 1 of #8588's checklist. The remaining findings in that issue (eslint v10 bump for the brace-expansion/minimatch chain, the @esbuild-kit->tsx trace, and the recharts v3 migration) are unrelated, each need their own scoping, and are now tracked as sub-issues #8608, #8609, #8610. --- package-lock.json | 20 ++++++++++---------- package.json | 3 ++- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4830c5d91..731f314ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16199,9 +16199,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "funding": [ { "type": "github", @@ -16952,9 +16952,9 @@ } }, "node_modules/postcss": { - "version": "8.5.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", - "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", "funding": [ { "type": "opencollective", @@ -16971,7 +16971,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -19509,9 +19509,9 @@ } }, "node_modules/tar": { - "version": "7.5.19", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.19.tgz", - "integrity": "sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==", + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { diff --git a/package.json b/package.json index 39bce03ad..84dced2a2 100644 --- a/package.json +++ b/package.json @@ -174,12 +174,13 @@ "@lovable.dev/vite-plugin-hmr-gate": "1.1.2", "esbuild": "^0.28.1", "ws": "^8.21.0", - "tar": "^7.5.19", + "tar": "^7.5.21", "js-yaml": "^4.3.0", "adm-zip": "^0.6.0", "fast-uri": "^3.1.4", "@hono/node-server": "^2.0.8", "sharp": "^0.35.0", + "postcss": "^8.5.18", "lovable-tagger@1.2.0": { "esbuild": "^0.28.1" }, From afc9a2b6de717cb521454e06bbfccc0debbed269 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sat, 25 Jul 2026 00:51:27 -0700 Subject: [PATCH 2/4] build: add the missing ui-kit build step to the local test:ci gate Discovered incidentally while running the full local gate for #8588: test/unit/check-ui-kit-package.test.ts's own regression guard needs packages/loopover-ui-kit/dist/ to exist, but the root test:ci script never built that workspace before test:coverage -- unlike the real CI validate-tests job, which already builds it via `npx turbo run build --filter=@loopover/ui-kit` right before its own coverage step (added in #8592 to close this exact gap, but only in the CI workflow, not the local script this repo's own contributing guide points everyone at). Anyone running `npm run test:ci` from a clean checkout hit a spurious failure that real CI never showed. Adds `npm run build --workspace @loopover/ui-kit` alongside the other per-package builds already in the chain (engine, discovery-index, mcp, miner), so the local script matches CI again. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 84dced2a2..b14e80c9f 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "test:smoke:browser:install": "playwright install chromium", "test:smoke:browser": "node --experimental-strip-types scripts/smoke-ui-browser.ts", "pretest:ci": "npm run check-node-version", - "test:ci": "git diff --check && npm run actionlint && npm run lint:composite-actions && npm run db:migrations:check && npm run db:schema-drift:check && npm run selfhost:env-reference:check && npm run miner:env-reference:check && npm run selfhost:validate-observability && npm run cf-typegen:check && npm run build --workspace @loopover/engine && npm run build --workspace @loopover/discovery-index && npm run build:mcp && npm run build:miner && npm run typecheck && npm run test:coverage && npm run test:engine-parity && npm run test:live-gate-parity && npm run test:driver-parity && npm run test --workspace @loopover/engine && npm run test:workers && npm run test:mcp-pack && npm run test:miner-pack && npm run test:engine-pack && npm run test:ui-kit-pack && npm run test:miner-deployment-docs-audit && npm run rees:test && npm run ui:openapi:check && npm run ui:openapi:settings-parity && npm run ui:version-audit && npm run docs:drift-check && npm run coverage-boltons:check && npm run branding-drift:check && npm run manifest:drift-check && npm run engine-parity:drift-check && npm run engines-nvmrc:check && npm run release-manifest:sync:check && npm run command-reference:check && npm run ui:lint && npm run ui:typecheck && npm run ui:test && npm run ui:build", + "test:ci": "git diff --check && npm run actionlint && npm run lint:composite-actions && npm run db:migrations:check && npm run db:schema-drift:check && npm run selfhost:env-reference:check && npm run miner:env-reference:check && npm run selfhost:validate-observability && npm run cf-typegen:check && npm run build --workspace @loopover/engine && npm run build --workspace @loopover/discovery-index && npm run build:mcp && npm run build:miner && npm run build --workspace @loopover/ui-kit && npm run typecheck && npm run test:coverage && npm run test:engine-parity && npm run test:live-gate-parity && npm run test:driver-parity && npm run test --workspace @loopover/engine && npm run test:workers && npm run test:mcp-pack && npm run test:miner-pack && npm run test:engine-pack && npm run test:ui-kit-pack && npm run test:miner-deployment-docs-audit && npm run rees:test && npm run ui:openapi:check && npm run ui:openapi:settings-parity && npm run ui:version-audit && npm run docs:drift-check && npm run coverage-boltons:check && npm run branding-drift:check && npm run manifest:drift-check && npm run engine-parity:drift-check && npm run engines-nvmrc:check && npm run release-manifest:sync:check && npm run command-reference:check && npm run ui:lint && npm run ui:typecheck && npm run ui:test && npm run ui:build", "test:release": "npm run test:ci && npm run changelog:check", "test:release:mcp": "npm run test:ci", "test:watch": "vitest", From bb044ce65c77ba4d6ddad443295ebe509a29fa16 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sat, 25 Jul 2026 00:51:40 -0700 Subject: [PATCH 3/4] fix(mcp): stop CLI stdout truncation on >64KB piped output Discovered incidentally while running the full local gate for #8588: test/unit/mcp-cli-profiles.test.ts's changelog test started failing with a JSON parse error once packages/loopover-mcp/CHANGELOG.md grew past the OS pipe buffer size (verified: `loopover-mcp changelog --json | wc -c` returned exactly 65536 bytes instead of the real 65603+ -- the classic 64KB pipe-buffer boundary). Root cause: process.stdout/stderr writes to a POSIX pipe are asynchronous, but the CLI entrypoint called process.exit() immediately after the command's async work resolved, cutting off any pending write larger than one pipe buffer's worth before it finished flushing. This silently truncates any command's output over ~64KB for any real consumer piping the CLI (not just this test) -- e.g. `loopover-mcp changelog --json | jq` would get invalid JSON. Fix: drain both streams (wait for their internal buffer to empty) right before calling process.exit(), instead of changing when/whether process.exit() itself is called -- keeps the existing fast-exit behavior for everything else (no risk of hanging on a lingering fetch keep-alive socket) while fixing the truncation at its actual source. --- packages/loopover-mcp/bin/loopover-mcp.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/loopover-mcp/bin/loopover-mcp.ts b/packages/loopover-mcp/bin/loopover-mcp.ts index 0fb025d8b..4baef2709 100644 --- a/packages/loopover-mcp/bin/loopover-mcp.ts +++ b/packages/loopover-mcp/bin/loopover-mcp.ts @@ -1673,14 +1673,26 @@ function stdioToolDescription(name: any) { return tool.description; } -/* v8 ignore next 8 -- the CLI dispatch runs only in the launched process (runAsCliEntrypoint); an in-process +/* v8 ignore next 5 -- draining stdout/stderr before exit only matters for the real launched process writing to + an OS pipe (large output can exceed the pipe's buffer, and a POSIX pipe write is asynchronous); the + in-process test harness below never spawns a subprocess, so this never runs there. */ +function flushStdio(): Promise { + const drained = (stream: NodeJS.WriteStream) => + stream.writableLength > 0 ? new Promise((resolve) => stream.once("drain", resolve)) : Promise.resolve(); + return Promise.all([drained(process.stdout), drained(process.stderr)]).then(() => undefined); +} + +/* v8 ignore next 11 -- the CLI dispatch runs only in the launched process (runAsCliEntrypoint); an in-process unit importer keeps it false and drives runCli/maintainCli directly instead (mcp-cli-plan-issues.test.ts). */ if (runAsCliEntrypoint && cliArgs[0] !== "--stdio") { try { const exitCode = await runCli(cliArgs); + await flushStdio(); process.exit(typeof exitCode === "number" ? exitCode : 0); } catch (error) { - process.exit(reportCliFailure(argsWantJson(cliArgs), describeCliError(error), 1)); + const code = reportCliFailure(argsWantJson(cliArgs), describeCliError(error), 1); + await flushStdio(); + process.exit(code); } } From a573c8db7ff831029457d716b7bc3f804a39c146 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sat, 25 Jul 2026 00:51:46 -0700 Subject: [PATCH 4/4] test(miner): raise timeout for build:verify syntax-check regression guard Discovered incidentally while running the full local gate for #8588: this test spawns a real subprocess (node --check over every dist/bin+dist/lib file in packages/loopover-miner) -- 5.5s in isolation, but it exceeded the default 15s testTimeout once under the full suite's parallel load. Genuinely necessary real subprocess work with no redundancy to cut, matching this repo's established real-subprocess timeout-flake pattern (agent-sdk-driver.test.ts, miner-attempt-worktree .test.ts, miner-repo-clone.test.ts, #6869/#6871): raise to the same evidence-based 60000ms ceiling already used for that class of test, rather than reflexively widening without a reason. --- test/unit/miner-package-skeleton.test.ts | 29 ++++++++++++++++-------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/test/unit/miner-package-skeleton.test.ts b/test/unit/miner-package-skeleton.test.ts index adc229700..686dee33e 100644 --- a/test/unit/miner-package-skeleton.test.ts +++ b/test/unit/miner-package-skeleton.test.ts @@ -52,16 +52,25 @@ describe("loopover-miner package skeleton (#2287)", () => { expect(miner.scripts["build:verify"]).toBe("node scripts/check-syntax.mjs"); }); - it("build:verify's syntax check actually covers the CLI bin entry points, not just lib/", () => { - // The pre-split build script explicitly node --check'd bin/loopover-miner.js and - // bin/loopover-miner-mcp.js by name; the glob-driven replacement must still reach them. Requires a - // prior build (dist/bin, dist/lib populated) -- same precondition as every other test in this file - // that reads real compiled output, and as the package's own real `npm run build`. - const result = spawnSync("node", ["scripts/check-syntax.mjs"], { cwd: minerRoot, encoding: "utf8" }); - expect(result.status).toBe(0); - expect(result.stdout).toContain("node --check passed for all"); - expect(result.stdout).toMatch(/passed for all \d+ files in dist\/bin\/ and dist\/lib\//); - }); + it( + "build:verify's syntax check actually covers the CLI bin entry points, not just lib/", + () => { + // The pre-split build script explicitly node --check'd bin/loopover-miner.js and + // bin/loopover-miner-mcp.js by name; the glob-driven replacement must still reach them. Requires a + // prior build (dist/bin, dist/lib populated) -- same precondition as every other test in this file + // that reads real compiled output, and as the package's own real `npm run build`. + // Genuinely real subprocess work (node --check over every dist/bin+dist/lib file), no redundancy to + // cut -- 5.5s in isolation but can exceed the default 15s testTimeout under the full suite's parallel + // load. 60000ms matches the same evidence-based ceiling already used for this repo's other + // real-subprocess timeout flakes (agent-sdk-driver.test.ts, miner-attempt-worktree.test.ts, + // miner-repo-clone.test.ts, #6869/#6871). + const result = spawnSync("node", ["scripts/check-syntax.mjs"], { cwd: minerRoot, encoding: "utf8" }); + expect(result.status).toBe(0); + expect(result.stdout).toContain("node --check passed for all"); + expect(result.stdout).toMatch(/passed for all \d+ files in dist\/bin\/ and dist\/lib\//); + }, + 60000, + ); it("starts the CLI bin with a node shebang", () => { const bin = readFileSync(join(minerRoot, "dist/bin/loopover-miner.js"), "utf8");