Files
iios/packages/iios-service/src/platform/platform.module.ts
T
maaz519 6dc9e4ffee feat: reject socket-scoped tokens on REST (the other half of realtime delegation)
The delegated socket token (aud=IIOS_REALTIME_AUDIENCE) was only narrow in one direction: the
gateway accepts ONLY that audience, but REST never checked the actor token's audience — so a
leaked browser socket token still drove privileged REST, i.e. a full actor token with extra steps.

RealtimeTokenRestGuard closes it, registered GLOBALLY (APP_GUARD) because every REST route is a
target — only 2 of 15 controllers sit behind ContextAttestationGuard, so inbox/media/interactions/
ai/... would otherwise stay open.

It is a decode-only REJECT filter, never an authenticator:
- does not verify signatures (controllers still call SessionVerifier); stripping `aud` to bypass it
  invalidates the signature downstream,
- no bearer -> pass through (health, metrics, HMAC adapter webhooks),
- ws context -> pass through (the gateway enforces the mirror rule),
- unset IIOS_REALTIME_AUDIENCE -> no-op, the same switch that turns on the socket half.

So one env now enables the whole boundary: socket accepts only iios-message, REST refuses it.
8 tests; typecheck + build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 20:29:59 +05:30

20 lines
705 B
TypeScript

import { Global, Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { PLATFORM_PORTS, LocalDevPorts } from './platform-ports';
import { RealtimeTokenRestGuard } from './realtime-token.guard';
/**
* Binds the platform ports (P1: the permissive in-service default), and registers the
* realtime-delegation REST guard globally — a socket-scoped token must not drive REST on ANY
* route, so it can't be per-controller (see realtime-token.guard).
*/
@Global()
@Module({
providers: [
{ provide: PLATFORM_PORTS, useClass: LocalDevPorts },
{ provide: APP_GUARD, useClass: RealtimeTokenRestGuard },
],
exports: [PLATFORM_PORTS],
})
export class PlatformModule {}