From 92dfd70d6a71e9f6ba1613c659b26b1183677b27 Mon Sep 17 00:00:00 2001 From: Philip D'Souza Date: Tue, 9 Jun 2026 16:12:58 +0100 Subject: [PATCH] fix(photon): production hardening for the gRPC-native iMessage channel (#42732) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(photon): override transitive CVEs in the sidecar deps `npm audit` flagged 7 high-severity transitive CVEs (protobufjs code injection GHSA-66ff-xgx4-vchm + outdated @opentelemetry OTLP exporters) pulled in via spectrum-ts -> @photon-ai/otel. npm's suggested fix downgrades spectrum-ts to a version that targets the decommissioned spectrum host, so instead pin patched versions via `overrides` (protobufjs 8.6.1, @opentelemetry/* 0.218.0) without touching spectrum-ts. `npm audit` -> 0; spectrum-ts + provider still import. * fix(photon): harden the sidecar bridge + bound the dedup cache - constant-time sidecar control-token comparison (was `!==`, timing-attackable). - cap the control-channel request body (2 MiB) so a compromised local peer can't OOM the sidecar. - wrap the inbound gRPC stream consumer in a re-subscribe loop with capped exponential backoff + jitter — if the async iterator throws/ends it would otherwise stop inbound forever (the adapter dedupes any replay). - add an unhandledRejection handler so a stray rejection logs instead of killing the process. - dedup cache (adapter) was a true bounded LRU only for expired entries; a burst of unique ids within the window grew it without limit. Evict oldest at the cap. * chore: add AUTHOR_MAP entry for PhilipAD --------- Co-authored-by: PhilipAD --- plugins/platforms/photon/adapter.py | 21 +- plugins/platforms/photon/sidecar/index.mjs | 74 ++++-- .../photon/sidecar/package-lock.json | 230 ++++++------------ plugins/platforms/photon/sidecar/package.json | 7 + scripts/release.py | 1 + .../plugins/platforms/photon/test_inbound.py | 14 ++ 6 files changed, 170 insertions(+), 177 deletions(-) diff --git a/plugins/platforms/photon/adapter.py b/plugins/platforms/photon/adapter.py index 92c900dbb2f..78902234b1b 100644 --- a/plugins/platforms/photon/adapter.py +++ b/plugins/platforms/photon/adapter.py @@ -414,14 +414,19 @@ class PhotonAdapter(BasePlatformAdapter): def _is_duplicate(self, msg_id: str) -> bool: now = time.time() - if len(self._seen_messages) > _DEDUP_MAX_SIZE: - cutoff = now - _DEDUP_WINDOW_SECONDS - self._seen_messages = { - k: v for k, v in self._seen_messages.items() if v > cutoff - } - if msg_id in self._seen_messages: - return True - self._seen_messages[msg_id] = now + seen = self._seen_messages + t = seen.get(msg_id) + if t is not None and now - t < _DEDUP_WINDOW_SECONDS: + return True # seen, unexpired + # New or expired: record and enforce a HARD size bound (evict oldest, + # insertion-order) so a burst of unique ids within the window can't grow + # the dict without limit — not just the expired-only prune. + if msg_id in seen: + del seen[msg_id] # refresh insertion order + seen[msg_id] = now + if len(seen) > _DEDUP_MAX_SIZE: + for old in list(seen.keys())[: len(seen) - _DEDUP_MAX_SIZE]: + del seen[old] return False async def _dispatch_inbound(self, event: Dict[str, Any]) -> None: diff --git a/plugins/platforms/photon/sidecar/index.mjs b/plugins/platforms/photon/sidecar/index.mjs index 1eceb538a17..d1c44d7c51b 100644 --- a/plugins/platforms/photon/sidecar/index.mjs +++ b/plugins/platforms/photon/sidecar/index.mjs @@ -40,6 +40,7 @@ // PHOTON_SIDECAR_BIND (default 127.0.0.1) import http from "node:http"; +import crypto from "node:crypto"; import { once } from "node:events"; const projectId = process.env.PHOTON_PROJECT_ID; @@ -254,32 +255,57 @@ async function normalizeEvent(space, message) { } } +// spectrum-ts handles in-session gRPC reconnects internally, but if the async +// iterator itself throws or ends, this consumer would stop forever. Wrap it in +// a re-subscribe loop with capped exponential backoff + jitter so inbound +// always recovers (the adapter dedupes any catch-up replay). (async () => { - try { - for await (const [space, message] of app.messages) { - // Only forward inbound messages (ignore our own outbound echoes). - if (message && message.direction && message.direction !== "inbound") { - continue; + let backoff = 1000; + for (;;) { + try { + for await (const [space, message] of app.messages) { + backoff = 1000; // healthy traffic — reset + // Only forward inbound messages (ignore our own outbound echoes). + if (message && message.direction && message.direction !== "inbound") { + continue; + } + rememberInboundSpace(space, message); + const event = await normalizeEvent(space, message); + if (!event) continue; + await deliver(JSON.stringify(event)); } - rememberInboundSpace(space, message); - const event = await normalizeEvent(space, message); - if (!event) continue; - await deliver(JSON.stringify(event)); + console.error("photon-sidecar: inbound stream ended — re-subscribing"); + } catch (e) { + console.error( + "photon-sidecar: inbound stream errored — restarting: " + + (e && e.message ? e.message : String(e)) + ); } - } catch (e) { - console.error( - "photon-sidecar: inbound stream errored: " + - (e && e.stack ? e.stack : String(e)) + await new Promise((r) => + setTimeout(r, backoff + Math.random() * backoff * 0.2) ); + backoff = Math.min(backoff * 2, 30000); } })(); // --------------------------------------------------------------------------- // HTTP control + inbound server (loopback only). +// Control-message bodies are tiny; cap the body so a compromised local peer +// can't OOM the sidecar by streaming an unbounded request (defence-in-depth on +// the loopback channel). +const MAX_BODY_BYTES = 2 * 1024 * 1024; // 2 MiB async function readBody(req) { const chunks = []; - for await (const chunk of req) chunks.push(chunk); + let size = 0; + for await (const chunk of req) { + size += chunk.length; + if (size > MAX_BODY_BYTES) { + req.destroy(); + throw new Error("request body too large"); + } + chunks.push(chunk); + } const raw = Buffer.concat(chunks).toString("utf-8"); if (!raw) return {}; try { @@ -386,8 +412,16 @@ async function resolveSpace(spaceId) { throw new Error(`unable to resolve space id ${spaceId}`); } +// Constant-time token comparison — don't leak the token via `!==` timing. +const _tokenBuf = Buffer.from(sharedToken); +function tokenOk(header) { + if (typeof header !== "string") return false; + const h = Buffer.from(header); + return h.length === _tokenBuf.length && crypto.timingSafeEqual(h, _tokenBuf); +} + const server = http.createServer(async (req, res) => { - if (req.headers["x-hermes-sidecar-token"] !== sharedToken) { + if (!tokenOk(req.headers["x-hermes-sidecar-token"])) { return unauthorized(res); } // Long-lived inbound NDJSON stream. @@ -496,3 +530,13 @@ async function shutdown(signal) { process.on("SIGINT", () => shutdown("SIGINT")); process.on("SIGTERM", () => shutdown("SIGTERM")); + +// Don't let a stray promise rejection take the process down silently — handlers +// catch their own errors, so log and keep serving (Python supervises restart on +// a real fatal exit). +process.on("unhandledRejection", (reason) => { + console.error( + "photon-sidecar: unhandledRejection: " + + (reason && reason.stack ? reason.stack : String(reason)) + ); +}); diff --git a/plugins/platforms/photon/sidecar/package-lock.json b/plugins/platforms/photon/sidecar/package-lock.json index 56b9aca48e4..8a19d1445dd 100644 --- a/plugins/platforms/photon/sidecar/package-lock.json +++ b/plugins/platforms/photon/sidecar/package-lock.json @@ -119,16 +119,15 @@ } }, "node_modules/@opentelemetry/exporter-logs-otlp-http": { - "version": "0.216.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.216.0.tgz", - "integrity": "sha512-8SUzQY/aExKkz6Ab3vOf6gu690Xk4wHH90dGwXinejQzazn5HCIRR7yPVU/2fEuiZ73R92MU4qI3djHfYP7NJg==", - "license": "Apache-2.0", + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.218.0.tgz", + "integrity": "sha512-Qx+4rpVHzgg89dawcWRHyt+XRXeLnhFz/qBtvggmjkcgPUdr+NAB0/u/eIPA8yAeJV0J80Vz43JZCh/XFvZFGw==", "dependencies": { - "@opentelemetry/api-logs": "0.216.0", + "@opentelemetry/api-logs": "0.218.0", "@opentelemetry/core": "2.7.1", - "@opentelemetry/otlp-exporter-base": "0.216.0", - "@opentelemetry/otlp-transformer": "0.216.0", - "@opentelemetry/sdk-logs": "0.216.0" + "@opentelemetry/otlp-exporter-base": "0.218.0", + "@opentelemetry/otlp-transformer": "0.218.0", + "@opentelemetry/sdk-logs": "0.218.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -137,15 +136,42 @@ "@opentelemetry/api": "^1.3.0" } }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/api-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz", + "integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/sdk-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz", + "integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, "node_modules/@opentelemetry/exporter-trace-otlp-http": { - "version": "0.216.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.216.0.tgz", - "integrity": "sha512-DhWjvj0PUPFwFnhOEivpum8sJzj6FTuyx88zff+oHVLUhfd6cLyw4AIai/F4j0PZqYZBFuMT/OTMUd9wdXnBEQ==", - "license": "Apache-2.0", + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.218.0.tgz", + "integrity": "sha512-8dqezsmPhtKitIK/eTipZhYl9EX2/gNQ5zUMhaz3uxEURwfkNf8IPvo6yNfrzbxdtpAOybS/+h7wmIWYqFSpiw==", "dependencies": { "@opentelemetry/core": "2.7.1", - "@opentelemetry/otlp-exporter-base": "0.216.0", - "@opentelemetry/otlp-transformer": "0.216.0", + "@opentelemetry/otlp-exporter-base": "0.218.0", + "@opentelemetry/otlp-transformer": "0.218.0", "@opentelemetry/resources": "2.7.1", "@opentelemetry/sdk-trace-base": "2.7.1" }, @@ -157,13 +183,12 @@ } }, "node_modules/@opentelemetry/otlp-exporter-base": { - "version": "0.216.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.216.0.tgz", - "integrity": "sha512-sSnvb5f+FYa4mfYxj03rmmUh+aDwo3jok62dgIWUDw8ZCUPzEbgtv/YhZyKUSlKNNey7Uc5xmJgmtTLLIV6UDQ==", - "license": "Apache-2.0", + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.218.0.tgz", + "integrity": "sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==", "dependencies": { "@opentelemetry/core": "2.7.1", - "@opentelemetry/otlp-transformer": "0.216.0" + "@opentelemetry/otlp-transformer": "0.218.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -173,18 +198,16 @@ } }, "node_modules/@opentelemetry/otlp-transformer": { - "version": "0.216.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.216.0.tgz", - "integrity": "sha512-g4Rb6sAsxQAo11eDjixfKxelruBsQFdJ8Wo23FCj7D6OXbidgXMu2xaRSYs4RdlomzAXSJuc86RcS3xmE8A6uA==", - "license": "Apache-2.0", + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.218.0.tgz", + "integrity": "sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==", "dependencies": { - "@opentelemetry/api-logs": "0.216.0", + "@opentelemetry/api-logs": "0.218.0", "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-logs": "0.216.0", + "@opentelemetry/sdk-logs": "0.218.0", "@opentelemetry/sdk-metrics": "2.7.1", - "@opentelemetry/sdk-trace-base": "2.7.1", - "protobufjs": "8.0.1" + "@opentelemetry/sdk-trace-base": "2.7.1" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -193,28 +216,32 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/protobufjs": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.0.1.tgz", - "integrity": "sha512-NWWCCscLjs+cOKF/s/XVNFRW7Yih0fdH+9brffR5NZCy8k42yRdl5KlWKMVXuI1vfCoy4o1z80XR/W/QUb3V3w==", - "hasInstallScript": true, - "license": "BSD-3-Clause", + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/api-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz", + "integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==", "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz", + "integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "node_modules/@opentelemetry/resources": { @@ -255,7 +282,6 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", - "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1" @@ -402,97 +428,12 @@ "node": ">=18" } }, - "node_modules/@photon-ai/whatsapp-business/node_modules/protobufjs": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.4.2.tgz", - "integrity": "sha512-64rfNzkWOZAIazXzpBFPWq6F9up6gMvTzjE2oWIzApx2N/dqVUEE7+bCn2+40780dFVtKOUab8QfxJ6KJDWbqA==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "long": "^5.3.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", - "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", - "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", - "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", - "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", - "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", - "license": "BSD-3-Clause" - }, "node_modules/@repeaterjs/repeater": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.6.tgz", "integrity": "sha512-Javneu5lsuhwNCryN+pXH93VPQ8g0dBX7wItHFgYiwQmzE1sVdg5tWHiOgHywzL2W21XQopa7IwIEnNbmeUJYA==", "license": "MIT" }, - "node_modules/@types/node": { - "version": "25.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", - "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", - "license": "MIT", - "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" - } - }, "node_modules/abort-controller-x": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/abort-controller-x/-/abort-controller-x-0.5.0.tgz", @@ -1306,23 +1247,10 @@ } }, "node_modules/protobufjs": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.1.tgz", - "integrity": "sha512-4K0myLaWL5EteuSAro91EGFgcfVgxb64Jx+7oDAY6GOkXD4M69yuSEljNcInGVCA5sOPxmZ/EqDLj2x0Q0+Ygg==", - "hasInstallScript": true, - "license": "BSD-3-Clause", + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.6.1.tgz", + "integrity": "sha512-s4qQPr4pU0W95iYnUInh95skjIg+3aM2sakYsw60QYanU+qWRDY2zQxOAQV6zU7ROJpSNDG9B+VSmk4dqdWWSA==", "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.1", - "@protobufjs/fetch": "^1.1.1", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.2", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", "long": "^5.3.2" }, "engines": { @@ -1651,12 +1579,6 @@ "node": ">=20.18.1" } }, - "node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", - "license": "MIT" - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/plugins/platforms/photon/sidecar/package.json b/plugins/platforms/photon/sidecar/package.json index d835e260784..522335e46b1 100644 --- a/plugins/platforms/photon/sidecar/package.json +++ b/plugins/platforms/photon/sidecar/package.json @@ -13,5 +13,12 @@ }, "dependencies": { "spectrum-ts": "^1.18.0" + }, + "overrides": { + "protobufjs": "8.6.1", + "@opentelemetry/otlp-transformer": "0.218.0", + "@opentelemetry/otlp-exporter-base": "0.218.0", + "@opentelemetry/exporter-trace-otlp-http": "0.218.0", + "@opentelemetry/exporter-logs-otlp-http": "0.218.0" } } diff --git a/scripts/release.py b/scripts/release.py index 3bdfad32c61..66678fb8e0c 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -45,6 +45,7 @@ ACP_REGISTRY_MANIFEST = REPO_ROOT / "acp_registry" / "agent.json" # Auto-extracted from noreply emails + manual overrides AUTHOR_MAP = { + "philipadsouza@gmail.com": "PhilipAD", "zhuhaoyu0909@icloud.com": "underthestars-zhy", "raysun12142006@gmail.com": "yanxue06", "alberto.regalado@ymail.com": "ARegalado1", diff --git a/tests/plugins/platforms/photon/test_inbound.py b/tests/plugins/platforms/photon/test_inbound.py index a31cfc15d9f..7b8c39723d5 100644 --- a/tests/plugins/platforms/photon/test_inbound.py +++ b/tests/plugins/platforms/photon/test_inbound.py @@ -292,6 +292,20 @@ def test_is_duplicate_window(monkeypatch: pytest.MonkeyPatch) -> None: assert adapter._is_duplicate("id-1") is True # still dup +def test_is_duplicate_hard_size_bound(monkeypatch: pytest.MonkeyPatch) -> None: + # A burst of unique ids within the window must not grow the dedup map past + # its bound — evict oldest (LRU), not only expired entries. + import plugins.platforms.photon.adapter as ad + + monkeypatch.setattr(ad, "_DEDUP_MAX_SIZE", 5) + adapter = _make_adapter(monkeypatch) + for i in range(100): + adapter._is_duplicate(f"id-{i}") + assert len(adapter._seen_messages) <= 5 + assert adapter._is_duplicate("id-99") is True # recent still deduped + assert adapter._is_duplicate("id-0") is False # oldest evicted + + def test_check_requirements_without_node(monkeypatch: pytest.MonkeyPatch) -> None: # If no node binary on PATH the adapter should refuse to start. from plugins.platforms.photon import adapter as adapter_mod