From 849e7ae802dcc1ccac484bf2e32a1b920a7fb0c0 Mon Sep 17 00:00:00 2001 From: SIAX Agent Date: Sat, 7 Nov 2026 14:55:59 +0100 Subject: [PATCH] =?UTF-8?q?fix(auth):=20internal-token=20requests=20f?= =?UTF-8?q?=C3=A5r=20verifierad=20tenant=20(P0=20tenant-isolering)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bevisat: POST /api/0pt/canonical/facts med X-Internal-Token och tenant_id 999999999999999999 returnerade 201 och skrev data i annan tenant. Orsak: internal-token-grenen returnerade next() utan x-0pt-verified-tenant, så assertTenantMatchesVerified blev no-op. Fix: INTERNAL_TENANT_ID (eller X-0pt-Internal-Tenant) sätts som verifierad tenant, vilket gör guard-enforcement identisk med verifierad Bearer. Varnar om variabeln saknas. --- middleware.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/middleware.ts b/middleware.ts index 1af30ef..cfd89f4 100644 --- a/middleware.ts +++ b/middleware.ts @@ -35,13 +35,31 @@ const INTERNAL_SERVICE_ROUTES: RegExp[] = [ ]; let _authDiagCount = 0; +let internalTenantWarned = false; export async function middleware(req: NextRequest): Promise { const path = req.nextUrl.pathname; if (EXEMPT_PATTERNS.some((re) => re.test(path))) return NextResponse.next(); if (INTERNAL_SERVICE_ROUTES.some((re) => re.test(path)) && verifyInternalServiceToken(req.headers)) { - return NextResponse.next(); + // P014/P0 tenant isolation (2026-11-07): the internal-token path used to + // return next() WITHOUT a verified tenant, which made + // assertTenantMatchesVerified a no-op and let a service principal write + // ANY tenant_id (proven: POST canonical/facts with tenant_id + // 999999999999999999 returned 201). When INTERNAL_TENANT_ID is configured + // the guard is now enforced exactly like a verified Bearer principal. + const forwarded = new Headers(req.headers); + const internalTenant = + req.headers.get('x-0pt-internal-tenant') ?? process.env.INTERNAL_TENANT_ID; + if (internalTenant) { + forwarded.set('x-0pt-verified-tenant', internalTenant); + } else if (!internalTenantWarned) { + internalTenantWarned = true; + console.warn( + '[0pt-auth] INTERNAL_TENANT_ID is unset: internal-token requests are not tenant-scoped (set it in production)', + ); + } + return NextResponse.next({ request: { headers: forwarded } }); } if (_authDiagCount < 5) {