fix(photon): production hardening for the gRPC-native iMessage channel (#42732)
* 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 <philipadsouza@gmail.com>
This commit is contained in:
co-authored by
PhilipAD
parent
b5421f4ba6
commit
92dfd70d6a
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user