From e1bd93c6f2976a18a56ba9728bfe4f2bc0c39158 Mon Sep 17 00:00:00 2001 From: Chris Scott <99081550+chriswritescode-dev@users.noreply.github.com> Date: Sun, 1 Feb 2026 10:37:01 -0500 Subject: [PATCH 1/3] Update dependencies for CVE security fixes - Update hono from ^4.10.1 to ^4.11.7 - Update react-router-dom from ^7.9.4 to ^7.13.0 All tests passing, no breaking changes detected --- backend/package.json | 2 +- frontend/package.json | 2 +- pnpm-lock.yaml | 53 +++++++++++++++++++++++++------------------ 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/backend/package.json b/backend/package.json index aacc088b..6646a42d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -22,7 +22,7 @@ "better-auth": "^1.4.17", "dotenv": "^17.2.3", "eventsource": "^4.1.0", - "hono": "^4.10.1", + "hono": "^4.11.7", "strip-json-comments": "^3.1.1", "zod": "^4.1.12" }, diff --git a/frontend/package.json b/frontend/package.json index 649652ba..d6d757fe 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -43,7 +43,7 @@ "react-dom": "^19.1.1", "react-hook-form": "^7.65.0", "react-markdown": "^10.1.0", - "react-router-dom": "^7.9.4", + "react-router-dom": "^7.13.0", "rehype-highlight": "^7.0.2", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 064dc4d6..d01b4cc7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,7 +26,7 @@ importers: version: 1.4.17(@better-auth/core@1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.2))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-auth@1.4.17(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vitest@3.2.4))(better-call@1.1.8(zod@4.3.2))(nanostores@1.1.0) '@hono/node-server': specifier: ^1.19.5 - version: 1.19.7(hono@4.11.3) + version: 1.19.7(hono@4.11.7) '@opencode-manager/shared': specifier: workspace:* version: link:../shared @@ -43,8 +43,8 @@ importers: specifier: ^4.1.0 version: 4.1.0 hono: - specifier: ^4.10.1 - version: 4.11.3 + specifier: ^4.11.7 + version: 4.11.7 strip-json-comments: specifier: ^3.1.1 version: 3.1.1 @@ -182,8 +182,8 @@ importers: specifier: ^10.1.0 version: 10.1.0(@types/react@19.2.7)(react@19.2.3) react-router-dom: - specifier: ^7.9.4 - version: 7.11.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^7.13.0 + version: 7.13.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) rehype-highlight: specifier: ^7.0.2 version: 7.0.2 @@ -278,14 +278,14 @@ importers: zod: specifier: ^4.1.12 version: 4.3.2 - optionalDependencies: - dotenv: - specifier: ^17.2.3 - version: 17.2.3 devDependencies: typescript: specifier: ^5 version: 5.9.3 + optionalDependencies: + dotenv: + specifier: ^17.2.3 + version: 17.2.3 packages: @@ -2689,8 +2689,8 @@ packages: resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} - hono@4.11.3: - resolution: {integrity: sha512-PmQi306+M/ct/m5s66Hrg+adPnkD5jiO6IjA7WhWw0gSBSo1EcRegwuI1deZ+wd5pzCGynCcn2DprnE4/yEV4w==} + hono@4.11.7: + resolution: {integrity: sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==} engines: {node: '>=16.9.0'} html-encoding-sniffer@6.0.0: @@ -3377,15 +3377,15 @@ packages: '@types/react': optional: true - react-router-dom@7.11.0: - resolution: {integrity: sha512-e49Ir/kMGRzFOOrYQBdoitq3ULigw4lKbAyKusnvtDu2t4dBX4AGYPrzNvorXmVuOyeakai6FUPW5MmibvVG8g==} + react-router-dom@7.13.0: + resolution: {integrity: sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' react-dom: '>=18' - react-router@7.11.0: - resolution: {integrity: sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==} + react-router@7.13.0: + resolution: {integrity: sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -4391,9 +4391,9 @@ snapshots: '@hexagon/base64@1.1.28': {} - '@hono/node-server@1.19.7(hono@4.11.3)': + '@hono/node-server@1.19.7(hono@4.11.7)': dependencies: - hono: 4.11.3 + hono: 4.11.7 '@hookform/resolvers@5.2.2(react-hook-form@7.69.0(react@19.2.3))': dependencies: @@ -5670,7 +5670,7 @@ snapshots: '@better-fetch/fetch': 1.1.21 '@noble/ciphers': 2.1.1 '@noble/hashes': 2.0.1 - better-call: 1.1.8(zod@4.3.2) + better-call: 1.1.8(zod@4.3.5) defu: 6.1.4 jose: 6.1.3 kysely: 0.28.10 @@ -5690,6 +5690,15 @@ snapshots: optionalDependencies: zod: 4.3.2 + better-call@1.1.8(zod@4.3.5): + dependencies: + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + rou3: 0.7.12 + set-cookie-parser: 2.7.2 + optionalDependencies: + zod: 4.3.5 + bidi-js@1.0.3: dependencies: require-from-string: 2.0.2 @@ -6497,7 +6506,7 @@ snapshots: highlight.js@11.11.1: {} - hono@4.11.3: {} + hono@4.11.7: {} html-encoding-sniffer@6.0.0: dependencies: @@ -7363,13 +7372,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 - react-router-dom@7.11.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + react-router-dom@7.13.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - react-router: 7.11.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react-router: 7.13.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - react-router@7.11.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + react-router@7.13.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: cookie: 1.1.1 react: 19.2.3 From 5074934280eef000a49de9b3176ba97de2cf0991 Mon Sep 17 00:00:00 2001 From: Chris Scott <99081550+chriswritescode-dev@users.noreply.github.com> Date: Sun, 1 Feb 2026 10:42:32 -0500 Subject: [PATCH 2/3] Refactor SSE to use Hono's built-in streamSSE --- backend/src/routes/sse.ts | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/backend/src/routes/sse.ts b/backend/src/routes/sse.ts index 980e2caf..23594442 100644 --- a/backend/src/routes/sse.ts +++ b/backend/src/routes/sse.ts @@ -1,5 +1,5 @@ import { Hono } from 'hono' -import { stream } from 'hono/streaming' +import { streamSSE } from 'hono/streaming' import { sseAggregator } from '../services/sse-aggregator' import { SSESubscribeSchema } from '@opencode-manager/shared/schemas' import { logger } from '../utils/logger' @@ -15,45 +15,32 @@ export function createSSERoutes() { const directories = directoriesParam ? directoriesParam.split(',').filter(Boolean) : [] const clientId = `client_${Date.now()}_${Math.random().toString(36).slice(2)}` - c.header('Content-Type', 'text/event-stream') - c.header('Cache-Control', 'no-cache, no-store, no-transform') - c.header('Connection', 'keep-alive') c.header('X-Accel-Buffering', 'no') - return stream(c, async (writer) => { - const encoder = new TextEncoder() - const writeSSE = (event: string, data: string) => { - const lines = [] - if (event) lines.push(`event: ${event}`) - lines.push(`data: ${data}`) - lines.push('') - lines.push('') - writer.write(encoder.encode(lines.join('\n'))) - } - + return streamSSE(c, async (stream) => { const cleanup = sseAggregator.addClient( clientId, - (event, data) => { - writeSSE(event, data) + async (event, data) => { + await stream.writeSSE({ event, data }) }, directories ) - const heartbeatInterval = setInterval(() => { + const heartbeatInterval = setInterval(async () => { try { - writeSSE('heartbeat', JSON.stringify({ timestamp: Date.now() })) + await stream.writeSSE({ event: 'heartbeat', data: JSON.stringify({ timestamp: Date.now() }) }) } catch { clearInterval(heartbeatInterval) } }, HEARTBEAT_INTERVAL_MS) - writer.onAbort(() => { + stream.onAbort(() => { clearInterval(heartbeatInterval) cleanup() }) try { - writeSSE('connected', JSON.stringify({ clientId, directories, ...sseAggregator.getConnectionStatus() })) + await stream.writeSSE({ event: 'connected', data: JSON.stringify({ clientId, directories, ...sseAggregator.getConnectionStatus() }) }) } catch (err) { logger.error(`Failed to send SSE connected event for ${clientId}:`, err) } From 2af91984aecd399d8ac111595245be117ffc4e33 Mon Sep 17 00:00:00 2001 From: Chris Scott <99081550+chriswritescode-dev@users.noreply.github.com> Date: Sun, 1 Feb 2026 10:54:04 -0500 Subject: [PATCH 3/3] Revert SSE implementation and update Docker upgrade script --- backend/src/routes/sse.ts | 29 +++++++++++++++++++++-------- scripts/docker-upgrade.sh | 4 ++-- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/backend/src/routes/sse.ts b/backend/src/routes/sse.ts index 23594442..980e2caf 100644 --- a/backend/src/routes/sse.ts +++ b/backend/src/routes/sse.ts @@ -1,5 +1,5 @@ import { Hono } from 'hono' -import { streamSSE } from 'hono/streaming' +import { stream } from 'hono/streaming' import { sseAggregator } from '../services/sse-aggregator' import { SSESubscribeSchema } from '@opencode-manager/shared/schemas' import { logger } from '../utils/logger' @@ -15,32 +15,45 @@ export function createSSERoutes() { const directories = directoriesParam ? directoriesParam.split(',').filter(Boolean) : [] const clientId = `client_${Date.now()}_${Math.random().toString(36).slice(2)}` + c.header('Content-Type', 'text/event-stream') + c.header('Cache-Control', 'no-cache, no-store, no-transform') + c.header('Connection', 'keep-alive') c.header('X-Accel-Buffering', 'no') - return streamSSE(c, async (stream) => { + return stream(c, async (writer) => { + const encoder = new TextEncoder() + const writeSSE = (event: string, data: string) => { + const lines = [] + if (event) lines.push(`event: ${event}`) + lines.push(`data: ${data}`) + lines.push('') + lines.push('') + writer.write(encoder.encode(lines.join('\n'))) + } + const cleanup = sseAggregator.addClient( clientId, - async (event, data) => { - await stream.writeSSE({ event, data }) + (event, data) => { + writeSSE(event, data) }, directories ) - const heartbeatInterval = setInterval(async () => { + const heartbeatInterval = setInterval(() => { try { - await stream.writeSSE({ event: 'heartbeat', data: JSON.stringify({ timestamp: Date.now() }) }) + writeSSE('heartbeat', JSON.stringify({ timestamp: Date.now() })) } catch { clearInterval(heartbeatInterval) } }, HEARTBEAT_INTERVAL_MS) - stream.onAbort(() => { + writer.onAbort(() => { clearInterval(heartbeatInterval) cleanup() }) try { - await stream.writeSSE({ event: 'connected', data: JSON.stringify({ clientId, directories, ...sseAggregator.getConnectionStatus() }) }) + writeSSE('connected', JSON.stringify({ clientId, directories, ...sseAggregator.getConnectionStatus() })) } catch (err) { logger.error(`Failed to send SSE connected event for ${clientId}:`, err) } diff --git a/scripts/docker-upgrade.sh b/scripts/docker-upgrade.sh index 8ed96737..a630828b 100755 --- a/scripts/docker-upgrade.sh +++ b/scripts/docker-upgrade.sh @@ -39,8 +39,8 @@ fi echo -e "${YELLOW}Stopping container...${NC}" $DOCKER_COMPOSE down -echo -e "${YELLOW}Rebuilding image...${NC}" -$DOCKER_COMPOSE build +echo -e "${YELLOW}Rebuilding image with no cache...${NC}" +$DOCKER_COMPOSE build --no-cache echo -e "${YELLOW}Starting container...${NC}" $DOCKER_COMPOSE up -d