From 286b0b09ecdf04700736462dfafa5abe93e9b663 Mon Sep 17 00:00:00 2001 From: Maurice Date: Sun, 8 Nov 2026 23:33:52 +0000 Subject: [PATCH] feat(managed): the managed attachment point declares the routes it serves without a session, and the boot gate allows exactly those Empty in this repository, as the module itself. An install that replaces the module adds its anonymous routes in the same file, and the gate still fails on a route missing there or an entry that went stale. --- server/src/nest/common/validate-route-guards.ts | 3 ++- server/src/nest/managed/managed-ext.module.ts | 10 ++++++++++ server/tests/unit/nest/managed-ext.test.ts | 7 ++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/server/src/nest/common/validate-route-guards.ts b/server/src/nest/common/validate-route-guards.ts index 5106a88d2..c8b01440b 100644 --- a/server/src/nest/common/validate-route-guards.ts +++ b/server/src/nest/common/validate-route-guards.ts @@ -2,6 +2,7 @@ import { CookieAuthGuard } from '../auth-core/cookie-auth.guard'; import { JwtAuthGuard } from '../auth-core/jwt-auth.guard'; import { OptionalJwtGuard } from '../auth-core/optional-jwt.guard'; import { IS_PUBLIC, OPTIONAL_AUTH } from '../auth-core/public.decorator'; +import { MANAGED_PUBLIC_ROUTES } from '../managed/managed-ext.module'; import { ApiTokenGuard } from '../public-api/api-token.guard'; import type { INestApplication } from '@nestjs/common'; import { METHOD_METADATA, PATH_METADATA, GUARDS_METADATA } from '@nestjs/common/constants'; @@ -181,7 +182,7 @@ export const ANONYMOUS_GUARDED_ROUTE_ALLOW_LIST: string[] = [ */ export function validateRouteGuards( app: INestApplication, - allowList: string[] = PUBLIC_ROUTE_ALLOW_LIST, + allowList: string[] = [...PUBLIC_ROUTE_ALLOW_LIST, ...MANAGED_PUBLIC_ROUTES], anonymousGuardedAllowList: string[] = ANONYMOUS_GUARDED_ROUTE_ALLOW_LIST, ): void { const entries = collectRouteGuards(app); diff --git a/server/src/nest/managed/managed-ext.module.ts b/server/src/nest/managed/managed-ext.module.ts index a781beb80..207dcc743 100644 --- a/server/src/nest/managed/managed-ext.module.ts +++ b/server/src/nest/managed/managed-ext.module.ts @@ -22,3 +22,13 @@ import { Module } from '@nestjs/common'; */ @Module({}) export class ManagedExtModule {} + +/** + * Routes of this module that answer without a session, as `Controller.handler`. + * + * Empty here, like the module. The boot gate (validate-route-guards.ts) adds + * them to its allow-list, so an install that replaces this file declares its + * anonymous surface in the same place it adds it, and the gate still fails on a + * route that is missing here or an entry that is stale. + */ +export const MANAGED_PUBLIC_ROUTES: readonly string[] = []; diff --git a/server/tests/unit/nest/managed-ext.test.ts b/server/tests/unit/nest/managed-ext.test.ts index f5956e682..13f79da91 100644 --- a/server/tests/unit/nest/managed-ext.test.ts +++ b/server/tests/unit/nest/managed-ext.test.ts @@ -7,7 +7,7 @@ * carries none of them. A stray controller or provider slipping in would be * invisible in review and obvious only to whoever it shipped to. */ -import { ManagedExtModule } from '../../../src/nest/managed/managed-ext.module'; +import { MANAGED_PUBLIC_ROUTES, ManagedExtModule } from '../../../src/nest/managed/managed-ext.module'; import { MODULE_METADATA } from '@nestjs/common/constants'; import { Test } from '@nestjs/testing'; @@ -25,6 +25,11 @@ describe('ManagedExtModule', () => { }).toEqual({ controllers: [], providers: [], imports: [], exports: [] }); }); + it('MANAGED-EXT-003: declares no route that answers without a session', () => { + // The boot gate adds these to its allow-list; the public build adds nothing. + expect(MANAGED_PUBLIC_ROUTES).toEqual([]); + }); + it('MANAGED-EXT-002: compiles as a module, so app.module can import it unconditionally', async () => { // Unconditionally on purpose: a conditional import would tie the module graph // to an env var, and the graph is built once per app while TREK_MANAGED is