diff --git a/package.json b/package.json index 47ce2b7..69fb67e 100644 --- a/package.json +++ b/package.json @@ -31,5 +31,8 @@ "husky": "9.1.7", "neostandard": "0.13.0", "tsx": "4.22.4" + }, + "dependencies": { + "mcp-remote": "0.1.38" } } diff --git a/packages/core/src/auth/auth-manager.ts b/packages/core/src/auth/auth-manager.ts index 7a1c477..577dfd6 100644 --- a/packages/core/src/auth/auth-manager.ts +++ b/packages/core/src/auth/auth-manager.ts @@ -1,5 +1,6 @@ import { randomUUID } from 'node:crypto' import { execFile } from 'node:child_process' +import { win32 } from 'node:path' import type { AuthConfig, Credentials, Logger, AuthConfirmation, HarnessType } from '../types.js' import { loadCredentials, saveCredentials, isExpired } from './token-storage.js' import { validateToken } from './token-validator.js' @@ -8,7 +9,7 @@ import { PermissionError, InvalidCredentialsError } from './errors.js' import { formatPluginError, toPluginError } from '../errors.js' import { deriveMcpUrlFromConsoleUrl } from './mcp-url.js' -function openBrowser (url: string, logger?: Logger): void { +export function openBrowser (url: string, logger?: Logger): void { try { // eslint-disable-next-line no-new new URL(url) @@ -16,9 +17,27 @@ function openBrowser (url: string, logger?: Logger): void { logger?.warn('auth.openBrowser.invalidUrl', { url }) return } - const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open' - const args = process.platform === 'win32' ? ['/c', 'start', url] : [url] - execFile(cmd, args, (err) => { + if (process.platform === 'win32') { + // Use ShellExecute directly instead of cmd /c start to avoid & being + // interpreted as a command separator on Windows. Resolve rundll32 from + // the Windows directory explicitly: a bare executable name can otherwise + // be found in the current working directory before the system directory. + const systemRoot = process.env.SystemRoot ?? process.env.WINDIR + // win32.isAbsolute accepts root-relative paths such as `\\Windows`, which + // would still depend on the current drive. Only accept a drive-qualified + // or UNC system root. + if (!systemRoot || !/^(?:[a-zA-Z]:[\\/]|\\\\[^\\/]+[\\/][^\\/]+)/.test(systemRoot)) { + logger?.warn('auth.openBrowser.failed', { error: 'Windows system root is unavailable' }) + return + } + const rundll32 = win32.join(systemRoot, 'System32', 'rundll32.exe') + execFile(rundll32, ['url.dll,FileProtocolHandler', url], (err) => { + if (err) logger?.warn('auth.openBrowser.failed', { error: err.message }) + }) + return + } + const cmd = process.platform === 'darwin' ? 'open' : 'xdg-open' + execFile(cmd, [url], (err) => { if (err) logger?.warn('auth.openBrowser.failed', { error: err.message }) }) } diff --git a/packages/core/test/integration/auth/auth-manager.test.ts b/packages/core/test/integration/auth/auth-manager.test.ts index c34e2d6..0f29354 100644 --- a/packages/core/test/integration/auth/auth-manager.test.ts +++ b/packages/core/test/integration/auth/auth-manager.test.ts @@ -28,9 +28,14 @@ const authConfig: AuthConfig = { } function getUrlFromExecFileCall (): URL { - const args = execFileCalls[execFileCalls.length - 1][1] as string[] - const urlStr = args.find((a: string) => a.startsWith('http'))! - return new URL(urlStr) + const call = execFileCalls[execFileCalls.length - 1] + const cmd = call[0] as string + const args = call[1] as string[] + const urlStr = cmd === 'rundll32' + ? args[args.length - 1] + : args.find((a: string) => a.startsWith('http') || a.startsWith('"http'))! + const cleaned = urlStr.replace(/^"|"$/g, '') + return new URL(cleaned) } function getStateFromExecFileCall (): string { @@ -510,9 +515,11 @@ describe('ensureAuthenticated - requiredPermissions', () => { }) describe('ensureAuthenticated - Windows browser launch', () => { - it('uses cmd /c start on Windows', { timeout: 10000 }, async () => { + it('uses the trusted rundll32 path with url.dll,FileProtocolHandler on Windows', { timeout: 10000 }, async () => { const originalPlatform = process.platform + const originalSystemRoot = process.env.SystemRoot Object.defineProperty(process, 'platform', { value: 'win32' }) + process.env.SystemRoot = 'C:\\Windows' try { const { saveCredentials } = await import('../../../src/auth/token-storage.js') @@ -539,16 +546,18 @@ describe('ensureAuthenticated - Windows browser launch', () => { await new Promise((resolve) => setTimeout(resolve, 50)) const lastCall = execFileCalls[execFileCalls.length - 1] - assert.strictEqual(lastCall[0], 'cmd') + assert.strictEqual(lastCall[0], 'C:\\Windows\\System32\\rundll32.exe') const lastArgs = lastCall[1] as string[] - assert.ok(lastArgs.includes('/c')) - assert.ok(lastArgs.includes('start')) + assert.strictEqual(lastArgs[0], 'url.dll,FileProtocolHandler') + assert.ok(lastArgs[1].startsWith('https://accounts.example.com/sign-in')) const state = getStateFromExecFileCall() await sendCallback(8767, state) await promise } finally { Object.defineProperty(process, 'platform', { value: originalPlatform }) + if (originalSystemRoot === undefined) delete process.env.SystemRoot + else process.env.SystemRoot = originalSystemRoot } }) }) diff --git a/packages/core/test/unit/auth/open-browser.test.ts b/packages/core/test/unit/auth/open-browser.test.ts new file mode 100644 index 0000000..2f630a1 --- /dev/null +++ b/packages/core/test/unit/auth/open-browser.test.ts @@ -0,0 +1,159 @@ +import { describe, it, beforeEach, afterEach } from 'node:test' +import assert from 'node:assert/strict' +import { createRequire, syncBuiltinESMExports } from 'node:module' + +const require = createRequire(import.meta.url) +const cp = require('node:child_process') +let moduleLoadId = 0 + +async function loadOpenBrowser () { + return await import(`../../../src/auth/auth-manager.js?open-browser-test=${moduleLoadId++}`) +} + +describe('openBrowser', () => { + let originalPlatform: PropertyDescriptor | undefined + let execFileCalls: unknown[][] + let originalExecFile: typeof cp.execFile + let originalSystemRoot: string | undefined + let originalWindir: string | undefined + + beforeEach(() => { + execFileCalls = [] + originalExecFile = cp.execFile + cp.execFile = (...args: unknown[]) => { + execFileCalls.push(args) + } + syncBuiltinESMExports() + originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform') + originalSystemRoot = process.env.SystemRoot + originalWindir = process.env.WINDIR + }) + + afterEach(() => { + cp.execFile = originalExecFile + syncBuiltinESMExports() + if (originalPlatform) { + Object.defineProperty(process, 'platform', originalPlatform) + } + if (originalSystemRoot === undefined) delete process.env.SystemRoot + else process.env.SystemRoot = originalSystemRoot + if (originalWindir === undefined) delete process.env.WINDIR + else process.env.WINDIR = originalWindir + }) + + it('opens the URL with the trusted rundll32 path on Windows', async () => { + Object.defineProperty(process, 'platform', { value: 'win32' }) + process.env.SystemRoot = 'C:\\Windows' + + const { openBrowser } = await loadOpenBrowser() + const url = 'https://accounts.example.com/sign-in?extension=nsolid-plugin&port=8765&state=abc' + openBrowser(url) + + assert.strictEqual(execFileCalls.length, 1) + const [cmd, args] = execFileCalls[0] as [string, string[]] + assert.strictEqual(cmd, 'C:\\Windows\\System32\\rundll32.exe') + assert.deepStrictEqual(args.slice(0, -1), ['url.dll,FileProtocolHandler']) + assert.strictEqual(args[args.length - 1], url) + }) + + it('does not launch a relative rundll32 when Windows system root is unavailable', async () => { + Object.defineProperty(process, 'platform', { value: 'win32' }) + delete process.env.SystemRoot + delete process.env.WINDIR + + const warnings: unknown[][] = [] + const logger = { warn: (...args: unknown[]) => warnings.push(args) } + const { openBrowser } = await loadOpenBrowser() + openBrowser('https://accounts.example.com/sign-in', logger as never) + + assert.strictEqual(execFileCalls.length, 0) + assert.deepStrictEqual(warnings, [['auth.openBrowser.failed', { + error: 'Windows system root is unavailable', + }]]) + }) + + it('does not use a root-relative Windows system directory', async () => { + Object.defineProperty(process, 'platform', { value: 'win32' }) + process.env.SystemRoot = '\\Windows' + + const warnings: unknown[][] = [] + const logger = { warn: (...args: unknown[]) => warnings.push(args) } + const { openBrowser } = await loadOpenBrowser() + openBrowser('https://accounts.example.com/sign-in', logger as never) + + assert.strictEqual(execFileCalls.length, 0) + assert.deepStrictEqual(warnings, [['auth.openBrowser.failed', { + error: 'Windows system root is unavailable', + }]]) + }) + + it('uses WINDIR when SystemRoot is unavailable', async () => { + Object.defineProperty(process, 'platform', { value: 'win32' }) + delete process.env.SystemRoot + process.env.WINDIR = 'D:\\Windows' + + const { openBrowser } = await loadOpenBrowser() + openBrowser('https://accounts.example.com/sign-in') + + const [cmd, args] = execFileCalls[0] as [string, string[]] + assert.strictEqual(cmd, 'D:\\Windows\\System32\\rundll32.exe') + assert.deepStrictEqual(args, ['url.dll,FileProtocolHandler', 'https://accounts.example.com/sign-in']) + }) + + it('preserves a Windows URL as one exact argument and reports spawn errors', async () => { + Object.defineProperty(process, 'platform', { value: 'win32' }) + process.env.SystemRoot = 'C:\\Windows' + cp.execFile = (...args: unknown[]) => { + execFileCalls.push(args) + const callback = args[2] as (err: Error | null) => void + callback(new Error('browser launch failed')) + } + syncBuiltinESMExports() + + const warnings: unknown[][] = [] + const logger = { warn: (...args: unknown[]) => warnings.push(args) } + const { openBrowser } = await loadOpenBrowser() + const url = 'https://accounts.example.com/sign-in?next=a%2Fb&value=one%20two&emoji=%F0%9F%9A%80' + openBrowser(url, logger as never) + + const [cmd, args] = execFileCalls[0] as [string, string[]] + assert.strictEqual(cmd, 'C:\\Windows\\System32\\rundll32.exe') + assert.deepStrictEqual(args, ['url.dll,FileProtocolHandler', url]) + assert.deepStrictEqual(warnings, [['auth.openBrowser.failed', { error: 'browser launch failed' }]]) + }) + + it('opens the URL with open on macOS', async () => { + Object.defineProperty(process, 'platform', { value: 'darwin' }) + + const { openBrowser } = await loadOpenBrowser() + const url = 'https://accounts.example.com/sign-in?extension=nsolid-plugin&port=8765&state=abc' + openBrowser(url) + + assert.strictEqual(execFileCalls.length, 1) + const [cmd, args] = execFileCalls[0] as [string, string[]] + assert.strictEqual(cmd, 'open') + assert.deepStrictEqual(args, [url]) + }) + + it('opens the URL with xdg-open on Linux', async () => { + Object.defineProperty(process, 'platform', { value: 'linux' }) + + const { openBrowser } = await loadOpenBrowser() + const url = 'https://accounts.example.com/sign-in?extension=nsolid-plugin&port=8765&state=abc' + openBrowser(url) + + assert.strictEqual(execFileCalls.length, 1) + const [cmd, args] = execFileCalls[0] as [string, string[]] + assert.strictEqual(cmd, 'xdg-open') + assert.deepStrictEqual(args, [url]) + }) + + it('does nothing for an invalid URL', async () => { + Object.defineProperty(process, 'platform', { value: 'win32' }) + + const { openBrowser } = await loadOpenBrowser() + openBrowser('not a url') + + assert.strictEqual(execFileCalls.length, 0) + }) +}) diff --git a/packages/core/test/unit/mcp/mcp-wrapper.test.ts b/packages/core/test/unit/mcp/mcp-wrapper.test.ts new file mode 100644 index 0000000..02333da --- /dev/null +++ b/packages/core/test/unit/mcp/mcp-wrapper.test.ts @@ -0,0 +1,152 @@ +import { afterEach, describe, it } from 'node:test' +import assert from 'node:assert/strict' +import { chmodSync, cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { spawnSync } from 'node:child_process' +import { tmpdir } from 'node:os' +import { delimiter, join } from 'node:path' +import { pathToFileURL } from 'node:url' + +// @ts-expect-error The repository's JavaScript generator intentionally has no TypeScript declarations. +import { generateMcpWrapper } from '../../../../../scripts/plugin-generators.mjs' + +const repoRoot = join(import.meta.dirname, '..', '..', '..', '..', '..') +const sourceWrapper = join(repoRoot, 'scripts', 'mcp-wrapper.js') +const url = 'https://example.test/a path?q=one&redirect=%PATH%"e="hello world"' +const token = 'tok en&%PATH%"quoted value"' +const temporaryPaths: string[] = [] + +afterEach(() => { + for (const temporaryPath of temporaryPaths.splice(0)) rmSync(temporaryPath, { recursive: true, force: true }) +}) + +function createWrapperFixture (wrapper: 'source' | 'generated'): { directory: string, wrapperPath: string, home: string, bin: string, output: string } { + const directory = mkdtempSync(join(tmpdir(), 'nsolid-mcp-wrapper-')) + temporaryPaths.push(directory) + const wrapperPath = join(directory, 'mcp-wrapper.mjs') + if (wrapper === 'source') cpSync(sourceWrapper, wrapperPath) + else writeFileSync(wrapperPath, generateMcpWrapper('claude')) + const home = join(directory, 'home') + const bin = join(directory, 'bin') + const output = join(directory, 'captured') + mkdirSync(join(home, '.agents'), { recursive: true }) + mkdirSync(bin) + writeFileSync(join(home, '.agents', '.nodesource-auth.json'), JSON.stringify({ + serviceToken: token, organizationId: 'org', consoleUrl: 'https://console.example.test', mcpUrl: url, expiresAt: '2099-01-01T00:00:00.000Z', + })) + return { directory, wrapperPath, home, bin, output } +} + +function wrapperEnvironment (fixture: ReturnType): NodeJS.ProcessEnv { + const environment: NodeJS.ProcessEnv = { ...process.env, HOME: fixture.home, USERPROFILE: fixture.home, PATH: `${fixture.bin}${delimiter}${process.env.PATH}`, NSOLID_TEST_OUTPUT: fixture.output } + delete environment.NSOLID_TEST_SYSTEM_ROOT + if (process.platform === 'win32') { + const hook = join(fixture.directory, 'override-exec-path.mjs') + writeFileSync(hook, [ + `Object.defineProperty(process, 'execPath', { value: ${JSON.stringify(join(fixture.bin, 'node.exe'))} })`, + 'if (process.env.NSOLID_TEST_SYSTEM_ROOT !== undefined) {', + ' process.env.SystemRoot = process.env.NSOLID_TEST_SYSTEM_ROOT', + ' delete process.env.NSOLID_TEST_SYSTEM_ROOT', + '}', + ].join('\n')) + const importHook = `--import=${pathToFileURL(hook).href}` + environment.NODE_OPTIONS = [process.env.NODE_OPTIONS, importHook].filter(Boolean).join(' ') + } + return environment +} + +describe('MCP wrapper fallback', () => { + it('bootstrap resolves the proxy from npx\'s node_modules directory', () => { + const directory = mkdtempSync(join(tmpdir(), 'nsolid-mcp-bootstrap-')) + temporaryPaths.push(directory) + const bin = join(directory, 'node_modules', '.bin') + const binName = process.platform === 'win32' ? 'mcp-remote.cmd' : 'mcp-remote' + const proxy = join(directory, 'node_modules', 'mcp-remote', 'dist', 'proxy.js') + const output = join(directory, 'argv.json') + mkdirSync(bin, { recursive: true }) + mkdirSync(join(directory, 'node_modules', 'mcp-remote', 'dist'), { recursive: true }) + writeFileSync(join(bin, binName), '') + writeFileSync(proxy, "const { writeFileSync } = require('node:fs')\nwriteFileSync(process.env.NSOLID_TEST_OUTPUT, JSON.stringify(process.argv.slice(2)))\n") + + const source = readFileSync(sourceWrapper, 'utf8') + const bootstrapLiteral = source.match(/const MCP_REMOTE_NPX_BOOTSTRAP = (".*")/) + assert.ok(bootstrapLiteral) + const bootstrap = `data:text/javascript;base64,${Buffer.from(JSON.parse(bootstrapLiteral[1])).toString('base64')}` + const payload = Buffer.from(JSON.stringify({ url, headers: { 'X-Nsolid-Service-Token': token } })).toString('base64url') + const result = spawnSync(process.execPath, ['--input-type=module', '--eval', 'await import(process.env.NSOLID_MCP_REMOTE_BOOTSTRAP)'], { + env: { ...process.env, PATH: `${bin}${delimiter}${process.env.PATH}`, NSOLID_MCP_REMOTE_BOOTSTRAP: bootstrap, NSOLID_MCP_REMOTE_PAYLOAD: payload, NSOLID_TEST_OUTPUT: output }, + encoding: 'utf8', + }) + assert.strictEqual(result.status, 0, result.stderr) + assert.deepStrictEqual(JSON.parse(readFileSync(output, 'utf8')), [url, '--header', `X-Nsolid-Service-Token:${token}`, '--transport', 'http-first', '--silent']) + }) + + for (const wrapper of ['source', 'generated'] as const) { + it(`${wrapper} wrapper preserves argv boundaries outside Windows`, { skip: process.platform === 'win32' }, () => { + const fixture = createWrapperFixture(wrapper) + const npx = join(fixture.bin, 'npx') + writeFileSync(npx, '#!/bin/sh\nprintf "%s\\n" "$@" > "$NSOLID_TEST_OUTPUT"\n') + chmodSync(npx, 0o755) + + const result = spawnSync(process.execPath, [fixture.wrapperPath, 'nsolid-console'], { env: wrapperEnvironment(fixture), encoding: 'utf8' }) + assert.strictEqual(result.status, 0, result.stderr) + assert.deepStrictEqual(readFileSync(fixture.output, 'utf8').trimEnd().split('\n'), [ + '-y', 'mcp-remote@0.1.38', url, '--header', `X-Nsolid-Service-Token:${token}`, '--transport', 'http-first', '--silent', + ]) + }) + + it(`${wrapper} wrapper keeps URL and headers out of cmd.exe on Windows`, { skip: process.platform !== 'win32' }, () => { + const fixture = createWrapperFixture(wrapper) + writeFileSync(join(fixture.bin, 'npx.cmd'), '@echo off\r\n(echo %NSOLID_MCP_REMOTE_PAYLOAD%&echo %NSOLID_MCP_REMOTE_BOOTSTRAP%) > "%NSOLID_TEST_OUTPUT%"\r\n') + const result = spawnSync(process.execPath, [fixture.wrapperPath, 'nsolid-console'], { env: wrapperEnvironment(fixture), encoding: 'utf8' }) + assert.strictEqual(result.status, 0, result.stderr) + + const [encodedPayload, bootstrap] = readFileSync(fixture.output, 'utf8').trimEnd().split(/\r?\n/) + assert.deepStrictEqual(JSON.parse(Buffer.from(encodedPayload, 'base64url').toString('utf8')), { + url, + headers: { 'X-Nsolid-Service-Token': token }, + }) + const bootstrapSource = Buffer.from(bootstrap.replace('data:text/javascript;base64,', ''), 'base64').toString('utf8') + assert.ok(!bootstrapSource.includes(url)) + assert.ok(!bootstrapSource.includes(token)) + assert.match(bootstrapSource, /mcp-remote executable was not installed by npx/) + }) + + it(`${wrapper} wrapper rejects a root-relative Windows system directory`, { skip: process.platform !== 'win32' }, () => { + const fixture = createWrapperFixture(wrapper) + writeFileSync(join(fixture.bin, 'npx.cmd'), '@echo off\r\nexit /b 0\r\n') + const environment = wrapperEnvironment(fixture) + // Setting SystemRoot before Node starts breaks Windows CSPRNG + // initialization. The preload applies this invalid value after startup, + // immediately before the wrapper validates it. + environment.NSOLID_TEST_SYSTEM_ROOT = '\\Windows' + + const result = spawnSync(process.execPath, [fixture.wrapperPath, 'nsolid-console'], { env: environment, encoding: 'utf8' }) + + assert.notStrictEqual(result.status, 0) + assert.match(result.stderr, /Could not locate the Windows system directory/) + }) + + it(`${wrapper} wrapper ignores npx.cmd in the project directory on Windows`, { skip: process.platform !== 'win32' }, () => { + const fixture = createWrapperFixture(wrapper) + const attacker = join(fixture.directory, 'attacker') + const mcpBin = join(fixture.directory, 'node_modules', '.bin') + const proxy = join(fixture.directory, 'node_modules', 'mcp-remote', 'dist', 'proxy.js') + mkdirSync(attacker) + mkdirSync(mcpBin, { recursive: true }) + mkdirSync(join(fixture.directory, 'node_modules', 'mcp-remote', 'dist'), { recursive: true }) + writeFileSync(join(attacker, 'npx.cmd'), '@echo off\r\necho malicious > "%NSOLID_TEST_OUTPUT%"\r\nexit /b 97\r\n') + writeFileSync(join(mcpBin, 'npx.cmd'), '@echo off\r\necho malicious-path > "%NSOLID_TEST_OUTPUT%"\r\nexit /b 98\r\n') + writeFileSync(join(fixture.bin, 'npx.cmd'), '@echo off\r\nnode %4 %5 %6\r\n') + writeFileSync(join(mcpBin, 'mcp-remote.cmd'), '') + writeFileSync(proxy, "const { writeFileSync } = require('node:fs')\nwriteFileSync(process.env.NSOLID_TEST_OUTPUT, JSON.stringify(process.argv.slice(2)))\n") + + const environment = wrapperEnvironment(fixture) + environment.PATH = `${mcpBin}${delimiter}${fixture.bin}${delimiter}${process.env.PATH}` + const result = spawnSync(process.execPath, [fixture.wrapperPath, 'nsolid-console'], { cwd: attacker, env: environment, encoding: 'utf8' }) + assert.strictEqual(result.status, 0, result.stderr) + assert.deepStrictEqual(JSON.parse(readFileSync(fixture.output, 'utf8')), [ + url, '--header', `X-Nsolid-Service-Token:${token}`, '--transport', 'http-first', '--silent', + ]) + }) + } +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 88b99d5..8135e57 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,10 @@ settings: importers: .: + dependencies: + mcp-remote: + specifier: 0.1.38 + version: 0.1.38 devDependencies: eslint: specifier: 9.39.4 @@ -345,6 +349,10 @@ packages: resolution: {integrity: sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -369,6 +377,9 @@ packages: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + array-includes@3.1.9: resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} engines: {node: '>= 0.4'} @@ -408,6 +419,10 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + body-parser@1.20.6: + resolution: {integrity: sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + brace-expansion@1.1.15: resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} @@ -415,6 +430,14 @@ packages: resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} engines: {node: 18 || 20 || >=22} + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -445,6 +468,21 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.0.7: + resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -461,6 +499,14 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -473,14 +519,34 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -489,6 +555,13 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + enhanced-resolve@5.23.0: resolution: {integrity: sha512-yJN/BOOLxcOW2aQgeif9mSnaUB8KtvmMMp56oA1kx1CRfBKbhZm2pJ+NBY+3eOboHxix8lfjWpHE0Ei5U8RbSA==} engines: {node: '>=10.13.0'} @@ -530,6 +603,9 @@ packages: engines: {node: '>=18'} hasBin: true + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -610,6 +686,14 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + express@4.22.2: + resolution: {integrity: sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==} + engines: {node: '>= 0.10.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -632,6 +716,10 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + finalhandler@1.3.2: + resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} + engines: {node: '>= 0.8'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -651,6 +739,14 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -742,11 +838,19 @@ packages: resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + husky@9.1.7: resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} engines: {node: '>=18'} hasBin: true + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -763,10 +867,17 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -799,6 +910,11 @@ packages: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -815,6 +931,11 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -863,6 +984,10 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -919,6 +1044,34 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mcp-remote@0.1.38: + resolution: {integrity: sha512-w+JU4U3CfG29TawXR4JLNQ9d1Un5nT8AGI65f/juCaqUdF/V6fS7wE4o7xNPbB8X58o46hRXEJgYglQMAKQs4w==} + hasBin: true + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + minimatch@10.2.5: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} @@ -926,12 +1079,19 @@ packages: minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + neostandard@0.13.0: resolution: {integrity: sha512-R3iglFr+Dla/8qFBqsMxBvcYBOgP6rAGw7uRHKMpM3bUP0wLDRzUstxtEI9RfEwn7xszE/UUnh8H090Ru4Z52A==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} @@ -971,6 +1131,14 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -999,6 +1167,10 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1010,6 +1182,9 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-to-regexp@0.1.13: + resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} + peowly@1.3.3: resolution: {integrity: sha512-5UmUtvuCv3KzBX2NuQw2uF28o0t8Eq4KkPRZfUCzJs+DiNVKw7OaYn29vNDgrt/Pggs23CPlSTqgzlhHJfpT0A==} engines: {node: '>=18.6.0', typescript: '>=5.8'} @@ -1029,10 +1204,26 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + engines: {node: '>=0.6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.3: + resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} + engines: {node: '>= 0.8'} + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -1056,10 +1247,17 @@ packages: engines: {node: '>= 0.4'} hasBin: true + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + safe-array-concat@1.1.4: resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} engines: {node: '>=0.4'} + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-push-apply@1.0.0: resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} @@ -1068,6 +1266,9 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -1077,6 +1278,14 @@ packages: engines: {node: '>=10'} hasBin: true + send@0.19.2: + resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} + engines: {node: '>= 0.8.0'} + + serve-static@1.16.3: + resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} + engines: {node: '>= 0.8.0'} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -1089,6 +1298,9 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1121,10 +1333,17 @@ packages: resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==} engines: {node: '>= 18'} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + strict-url-sanitise@0.0.1: + resolution: {integrity: sha512-nuFtF539K8jZg3FjaWH/L8eocCR6gegz5RDOsaWxfdbF5Jqr2VXWxZayjTwUzsWJDC91k2EbnJXp6FuWW+Z4hg==} + string.prototype.matchall@4.0.12: resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} engines: {node: '>= 0.4'} @@ -1164,6 +1383,10 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -1184,6 +1407,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -1219,13 +1446,29 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici@7.29.0: + resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} + engines: {node: '>=20.18.1'} + unicorn-magic@0.3.0: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -1255,6 +1498,10 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -1525,6 +1772,11 @@ snapshots: '@typescript-eslint/types': 8.61.0 eslint-visitor-keys: 5.0.1 + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: acorn: 8.16.0 @@ -1549,6 +1801,8 @@ snapshots: call-bound: 1.0.4 is-array-buffer: 3.0.5 + array-flatten@1.1.1: {} + array-includes@3.1.9: dependencies: call-bind: 1.0.9 @@ -1611,6 +1865,23 @@ snapshots: balanced-match@4.0.4: {} + body-parser@1.20.6: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.15.3 + raw-body: 2.5.3 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + brace-expansion@1.1.15: dependencies: balanced-match: 1.0.2 @@ -1620,6 +1891,12 @@ snapshots: dependencies: balanced-match: 4.0.4 + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + bytes@3.1.2: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -1652,6 +1929,16 @@ snapshots: concat-map@0.0.1: {} + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookie-signature@1.0.7: {} + + cookie@0.7.2: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -1676,24 +1963,41 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + debug@2.6.9: + dependencies: + ms: 2.0.0 + debug@4.4.3: dependencies: ms: 2.1.3 deep-is@0.1.4: {} + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 + define-lazy-prop@3.0.0: {} + define-properties@1.2.1: dependencies: define-data-property: 1.1.4 has-property-descriptors: 1.0.2 object-keys: 1.1.1 + depd@2.0.0: {} + + destroy@1.2.0: {} + doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -1704,6 +2008,10 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + ee-first@1.1.1: {} + + encodeurl@2.0.0: {} + enhanced-resolve@5.23.0: dependencies: graceful-fs: 4.2.11 @@ -1839,6 +2147,8 @@ snapshots: '@esbuild/win32-ia32': 0.28.0 '@esbuild/win32-x64': 0.28.0 + escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} eslint-compat-utils@0.5.1(eslint@9.39.4): @@ -1963,6 +2273,44 @@ snapshots: esutils@2.0.3: {} + etag@1.8.1: {} + + express@4.22.2: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.6 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.0.7 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.2 + fresh: 0.5.2 + http-errors: 2.0.1 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.13 + proxy-addr: 2.0.7 + qs: 6.15.3 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.2 + serve-static: 1.16.3 + setprototypeof: 1.2.0 + statuses: 2.0.2 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} @@ -1977,6 +2325,18 @@ snapshots: dependencies: flat-cache: 4.0.1 + finalhandler@1.3.2: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -1998,6 +2358,10 @@ snapshots: dependencies: is-callable: 1.2.7 + forwarded@0.2.0: {} + + fresh@0.5.2: {} + fsevents@2.3.3: optional: true @@ -2087,8 +2451,20 @@ snapshots: dependencies: function-bind: 1.1.2 + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + husky@9.1.7: {} + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -2100,12 +2476,16 @@ snapshots: imurmurhash@0.1.4: {} + inherits@2.0.4: {} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 hasown: 2.0.4 side-channel: 1.1.1 + ipaddr.js@1.9.1: {} + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.9 @@ -2146,6 +2526,8 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-docker@3.0.0: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -2164,6 +2546,10 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-map@2.0.3: {} is-negative-zero@2.0.3: {} @@ -2212,6 +2598,10 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + isarray@2.0.5: {} isexe@2.0.0: {} @@ -2269,6 +2659,29 @@ snapshots: math-intrinsics@1.1.0: {} + mcp-remote@0.1.38: + dependencies: + express: 4.22.2 + open: 10.2.0 + strict-url-sanitise: 0.0.1 + undici: 7.29.0 + transitivePeerDependencies: + - supports-color + + media-typer@0.3.0: {} + + merge-descriptors@1.0.3: {} + + methods@1.1.2: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + minimatch@10.2.5: dependencies: brace-expansion: 5.0.6 @@ -2277,10 +2690,14 @@ snapshots: dependencies: brace-expansion: 1.1.15 + ms@2.0.0: {} + ms@2.1.3: {} natural-compare@1.4.0: {} + negotiator@0.6.3: {} + neostandard@0.13.0(eslint@9.39.4)(typescript@5.9.3): dependencies: '@humanwhocodes/gitignore-to-minimatch': 1.0.2 @@ -2340,6 +2757,17 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.2 + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + open@10.2.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -2375,12 +2803,16 @@ snapshots: dependencies: callsites: 3.1.0 + parseurl@1.3.3: {} + path-exists@4.0.0: {} path-key@3.1.1: {} path-parse@1.0.7: {} + path-to-regexp@0.1.13: {} + peowly@1.3.3: {} picomatch@4.0.4: {} @@ -2395,8 +2827,27 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + punycode@2.3.1: {} + qs@6.15.3: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + + range-parser@1.2.1: {} + + raw-body@2.5.3: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + react-is@16.13.1: {} reflect.getprototypeof@1.0.10: @@ -2432,6 +2883,8 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + run-applescript@7.1.0: {} + safe-array-concat@1.1.4: dependencies: call-bind: 1.0.9 @@ -2440,6 +2893,8 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 + safe-buffer@5.2.1: {} + safe-push-apply@1.0.0: dependencies: es-errors: 1.3.0 @@ -2451,10 +2906,39 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safer-buffer@2.1.2: {} + semver@6.3.1: {} semver@7.8.4: {} + send@0.19.2: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.3: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2 + transitivePeerDependencies: + - supports-color + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -2477,6 +2961,8 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.2 + setprototypeof@1.2.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -2515,11 +3001,15 @@ snapshots: smol-toml@1.6.1: {} + statuses@2.0.2: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 internal-slot: 1.1.0 + strict-url-sanitise@0.0.1: {} + string.prototype.matchall@4.0.12: dependencies: call-bind: 1.0.9 @@ -2580,6 +3070,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + toidentifier@1.0.1: {} + ts-api-utils@2.5.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -2599,6 +3091,11 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -2654,12 +3151,20 @@ snapshots: undici-types@6.21.0: {} + undici@7.29.0: {} + unicorn-magic@0.3.0: {} + unpipe@1.0.0: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 + utils-merge@1.0.1: {} + + vary@1.1.2: {} + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -2712,6 +3217,10 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.1 + yocto-queue@0.1.0: {} yocto-queue@1.2.2: {} diff --git a/scripts/mcp-wrapper.js b/scripts/mcp-wrapper.js index a726b77..31fe524 100644 --- a/scripts/mcp-wrapper.js +++ b/scripts/mcp-wrapper.js @@ -9,6 +9,7 @@ import { pathToFileURL } from 'node:url' const AUTH_FILE = path.join(os.homedir(), '.agents', '.nodesource-auth.json') const SETUP_COMMAND = 'npx -y nsolid-plugin setup --harness ' +const MCP_REMOTE_NPX_BOOTSTRAP = "import{existsSync}from'node:fs';import path from'node:path';import{pathToFileURL}from'node:url';const payload=JSON.parse(Buffer.from(process.env.NSOLID_MCP_REMOTE_PAYLOAD,'base64url'));const binName=process.platform==='win32'?'mcp-remote.cmd':'mcp-remote';const binDir=process.env.PATH.split(path.delimiter).find(dir=>existsSync(path.join(dir,binName)));if(!binDir)throw new Error('mcp-remote executable was not installed by npx');const proxyPath=path.resolve(binDir,'..','mcp-remote','dist','proxy.js');const args=Object.entries(payload.headers).flatMap(([key,value])=>['--header',key+':'+value]);process.argv=[process.execPath,proxyPath,payload.url,...args,'--transport','http-first','--silent'];await import(pathToFileURL(proxyPath).href)" const SERVER_NAMES = new Set(["nsolid-console","ns-benchmark","ncm"]) const serverName = process.argv[2] @@ -115,16 +116,67 @@ async function runMcpRemote (url, headers) { } } - const child = spawn('npx', ['-y', 'mcp-remote@0.1.38', url, ...headerArgs, '--transport', 'http-first', '--silent'], { + const fallback = getMcpRemoteFallback(url, headers) + const options = { stdio: 'inherit', - env: process.env, - }) + ...fallback.options, + windowsHide: true, + } + const child = fallback.args.length === 0 + ? spawn(fallback.command, options) + : spawn(fallback.command, fallback.args, options) await new Promise((resolve, reject) => { child.on('error', reject) child.on('exit', (code) => code === 0 ? resolve() : reject(new Error('mcp-remote exited with code ' + (code ?? 1)))) }) } +function getMcpRemoteFallback (url, headers) { + if (process.platform !== 'win32') { + const headerArgs = Object.entries(headers).flatMap(([key, value]) => ['--header', `${key}:${value}`]) + return { + command: 'npx', + args: ['-y', 'mcp-remote@0.1.38', url, ...headerArgs, '--transport', 'http-first', '--silent'], + options: { shell: false, env: process.env }, + } + } + + // A .cmd file needs cmd.exe. Keep its command line constant and move all + // untrusted values into an encoded environment payload for Node to decode. + const npxCmd = resolveWindowsNpxCmd() + const payload = Buffer.from(JSON.stringify({ url, headers })).toString('base64url') + const bootstrap = `data:text/javascript;base64,${Buffer.from(MCP_REMOTE_NPX_BOOTSTRAP).toString('base64')}` + return { + command: '.\\npx.cmd -y --package=mcp-remote@0.1.38 node --input-type=module --eval "await import(process.env.NSOLID_MCP_REMOTE_BOOTSTRAP)"', + args: [], + options: { + shell: getWindowsCmdShell(), + cwd: path.dirname(npxCmd), + env: { ...process.env, NSOLID_MCP_REMOTE_PAYLOAD: payload, NSOLID_MCP_REMOTE_BOOTSTRAP: bootstrap }, + }, + } +} + +function resolveWindowsNpxCmd () { + // Node's own directory is already inside the trust boundary: this process + // was launched from it. Do not search PATH, which may contain project-owned + // .bin directories or other attacker-controlled entries. + const npxCmd = path.join(path.dirname(process.execPath), 'npx.cmd') + if (existsSync(npxCmd)) return npxCmd + throw new Error(`Could not locate npx.cmd next to Node.js at ${npxCmd}. Install Node.js with npm.`) +} + +function getWindowsCmdShell () { + const windowsRoot = process.env.SystemRoot ?? process.env.WINDIR + const root = windowsRoot ? path.win32.parse(windowsRoot).root : '' + if (!windowsRoot || !path.win32.isAbsolute(windowsRoot) || root.length === 1) { + throw new Error('Could not locate the Windows system directory.') + } + const shell = path.join(windowsRoot, 'System32', 'cmd.exe') + if (!existsSync(shell)) throw new Error(`Could not locate Windows command shell at ${shell}.`) + return shell +} + function fail (message) { console.error(`[nsolid-plugin] ${message}`) process.exit(1) diff --git a/scripts/plugin-generators.mjs b/scripts/plugin-generators.mjs index 42b5e5f..f9fbd91 100644 --- a/scripts/plugin-generators.mjs +++ b/scripts/plugin-generators.mjs @@ -153,6 +153,7 @@ import { pathToFileURL } from 'node:url' const AUTH_FILE = path.join(os.homedir(), '.agents', '.nodesource-auth.json') const SETUP_COMMAND = 'npx -y nsolid-plugin setup --harness ${harness}' +const MCP_REMOTE_NPX_BOOTSTRAP = "import{existsSync}from'node:fs';import path from'node:path';import{pathToFileURL}from'node:url';const payload=JSON.parse(Buffer.from(process.env.NSOLID_MCP_REMOTE_PAYLOAD,'base64url'));const binName=process.platform==='win32'?'mcp-remote.cmd':'mcp-remote';const binDir=process.env.PATH.split(path.delimiter).find(dir=>existsSync(path.join(dir,binName)));if(!binDir)throw new Error('mcp-remote executable was not installed by npx');const proxyPath=path.resolve(binDir,'..','mcp-remote','dist','proxy.js');const args=Object.entries(payload.headers).flatMap(([key,value])=>['--header',key+':'+value]);process.argv=[process.execPath,proxyPath,payload.url,...args,'--transport','http-first','--silent'];await import(pathToFileURL(proxyPath).href)" const SERVER_NAMES = new Set(${JSON.stringify(serverNames)}) const serverName = process.argv[2] @@ -259,16 +260,67 @@ async function runMcpRemote (url, headers) { } } - const child = spawn('npx', ['-y', 'mcp-remote@0.1.38', url, ...headerArgs, '--transport', 'http-first', '--silent'], { + const fallback = getMcpRemoteFallback(url, headers) + const options = { stdio: 'inherit', - env: process.env, - }) + ...fallback.options, + windowsHide: true, + } + const child = fallback.args.length === 0 + ? spawn(fallback.command, options) + : spawn(fallback.command, fallback.args, options) await new Promise((resolve, reject) => { child.on('error', reject) child.on('exit', (code) => code === 0 ? resolve() : reject(new Error('mcp-remote exited with code ' + (code ?? 1)))) }) } +function getMcpRemoteFallback (url, headers) { + if (process.platform !== 'win32') { + const headerArgs = Object.entries(headers).flatMap(([key, value]) => ['--header', \`\${key}:\${value}\`]) + return { + command: 'npx', + args: ['-y', 'mcp-remote@0.1.38', url, ...headerArgs, '--transport', 'http-first', '--silent'], + options: { shell: false, env: process.env }, + } + } + + // A .cmd file needs cmd.exe. Keep its command line constant and move all + // untrusted values into an encoded environment payload for Node to decode. + const npxCmd = resolveWindowsNpxCmd() + const payload = Buffer.from(JSON.stringify({ url, headers })).toString('base64url') + const bootstrap = \`data:text/javascript;base64,\${Buffer.from(MCP_REMOTE_NPX_BOOTSTRAP).toString('base64')}\` + return { + command: '.\\\\npx.cmd -y --package=mcp-remote@0.1.38 node --input-type=module --eval "await import(process.env.NSOLID_MCP_REMOTE_BOOTSTRAP)"', + args: [], + options: { + shell: getWindowsCmdShell(), + cwd: path.dirname(npxCmd), + env: { ...process.env, NSOLID_MCP_REMOTE_PAYLOAD: payload, NSOLID_MCP_REMOTE_BOOTSTRAP: bootstrap }, + }, + } +} + +function resolveWindowsNpxCmd () { + // Node's own directory is already inside the trust boundary: this process + // was launched from it. Do not search PATH, which may contain project-owned + // .bin directories or other attacker-controlled entries. + const npxCmd = path.join(path.dirname(process.execPath), 'npx.cmd') + if (existsSync(npxCmd)) return npxCmd + throw new Error(\`Could not locate npx.cmd next to Node.js at \${npxCmd}. Install Node.js with npm.\`) +} + +function getWindowsCmdShell () { + const windowsRoot = process.env.SystemRoot ?? process.env.WINDIR + const root = windowsRoot ? path.win32.parse(windowsRoot).root : '' + if (!windowsRoot || !path.win32.isAbsolute(windowsRoot) || root.length === 1) { + throw new Error('Could not locate the Windows system directory.') + } + const shell = path.join(windowsRoot, 'System32', 'cmd.exe') + if (!existsSync(shell)) throw new Error(\`Could not locate Windows command shell at \${shell}.\`) + return shell +} + function fail (message) { console.error(\`[nsolid-plugin] \${message}\`) process.exit(1)