Fynd (2026-11-07):POST /api/0pt/canonical/facts med X-Internal-Token och tenant_id=999999999999999999 gav HTTP 201 och skrev data i en annan tenant.
Orsak:middleware.ts internal-token-grenen returnerade next() utan att sätta x-0pt-verified-tenant. assertTenantMatchesVerified blev därför en no-op.
Fix: sätter x-0pt-verified-tenant från X-0pt-Internal-Tenant (eller INTERNAL_TENANT_ID), och varnar när ingen tenant kan verifieras.
Bevis: bogus-tenant-data (1 relation, 2 facts, 3 entities) städades bort och verifierades 0 kvar. Efter fix ska samma anrop nekas.
Åtgärd krävs: merge + deploy av 0pt-api. Ingen dataändring i övrigt.
## P0 — tenant-isolering för internal-token-anrop
**Fynd (2026-11-07):** `POST /api/0pt/canonical/facts` med `X-Internal-Token` och `tenant_id=999999999999999999` gav **HTTP 201** och skrev data i en annan tenant.
**Orsak:** `middleware.ts` internal-token-grenen returnerade `next()` utan att sätta `x-0pt-verified-tenant`. `assertTenantMatchesVerified` blev därför en no-op.
**Fix:** sätter `x-0pt-verified-tenant` från `X-0pt-Internal-Tenant` (eller `INTERNAL_TENANT_ID`), och varnar när ingen tenant kan verifieras.
**Bevis:** bogus-tenant-data (1 relation, 2 facts, 3 entities) städades bort och verifierades 0 kvar. Efter fix ska samma anrop nekas.
**Åtgärd krävs:** merge + deploy av 0pt-api. Ingen dataändring i övrigt.
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.
CODEOWNERS rules
requested review from sax3l 2026-11-07 14:16:03 +00:00
The first revision of this fix copied a CLIENT header (X-0pt-Internal-Tenant)
or a single INTERNAL_TENANT_ID into x-0pt-verified-tenant. That is not an
authorization source: any holder of the shared internal token could still name
any tenant, leaving the original cross-tenant write reachable through a
different header.
- INTERNAL_SERVICE_TENANTS (server env) is now the only tenant authority for
the X-Internal-Token principal; empty => no authority (fail closed, 403).
- Middleware strips inbound x-0pt-verified-{tenant,sub} and the service scope
header on every path, including the dev escape hatch.
- x-0pt-verified-service-tenants carries the allowlist; the tenant guard checks
membership and denies anything outside it.
- POST /api/0pt/quality/bridge-run now runs the same tenant guard as
canonical/facts (it auto-provisions tenant-scoped rules).
- 16 new regression tests; auth + cross-tenant + canonical + bridge suites
87/87; tsc --noEmit clean.
Verdikt på första revisionen (849e7ae): BLOCKERAD. Klientheadern var behörighetskälla.
Vad som var fel
req.headers.get('x-0pt-internal-tenant') ?? process.env.INTERNAL_TENANT_ID kopierades rakt in i x-0pt-verified-tenant. Vem som helst med den delade X-Internal-Token kunde därmed välja tenant i en request-header — samma cross-tenant-skrivning som fixen skulle stänga, bara via ett annat fält. POST canonical/facts tenant_id=999999999999999999 hade fortfarande gett 201.
X-0pt-Internal-Tenant är klientkontrollerad och därmed inte en behörighetskälla (krav 2 och 4).
INTERNAL_TENANT_ID som global fallback band inte tjänsten till en verifierad identitet eller ett per-route-scope; en enda miljövariabel blev "alla tenants".
Internal-pathen skickade vidare req.headers orörda — en klient-konstruerad x-0pt-verified-tenant/x-0pt-verified-sub kunde passera på alla vägar som inte skrev över dem (bl.a. dev-läget).
POST /api/0pt/quality/bridge-run tar tenant_id ur body och auto-provisionerar tenant-scopade regler — den saknade helt tenant-guard.
INTERNAL_SERVICE_TENANTS (server-env, kommaseparerad allowlist) är nu enda källan till tenant-auktoritet för X-Internal-Token-principalen. Tom/osatt ⇒ ingen tenant-auktoritet ⇒ tenant-scopade skrivningar nekas med 403 (fail closed). Tenant-lösa maskinrutter (evidence-outbox/dispatch, er/candidates, search/definitions) påverkas inte.
Middleware strippar inkommande x-0pt-verified-tenant, x-0pt-verified-sub och x-0pt-verified-service-tenants på alla vägar (intern token, session, Bearer, dev-läge) innan den sätter dem själv.
x-0pt-verified-service-tenants bär allowlisten; assertTenantMatchesVerified kontrollerar medlemskap och nekar allt utanför scope. En (1) allowlistad tenant sätts även som x-0pt-verified-tenant så befintliga single-tenant-kontroller fortsätter fungera.
Bridge-run fick samma guard som canonical/facts, före ensureBaselineRules.
X-0pt-Internal-Tenant är uttryckligen inte ett kontrakt och läses aldrig.
16 nya regressionstester (tests/auth/internal-token-tenant-scope.test.ts): förfalskad header ignoreras/stripas, tom scope ⇒ 403, cross-tenant ⇒ 403, inom scope ⇒ OK, Bearer/dev oförändrat.
Merge först efter ägargodkännande, och sätt INTERNAL_SERVICE_TENANTS=379976258701823235 (t_0pt) i prod-secreten innan deploy. Utan den variabeln nekar den interna principalen tenant-scopade skrivningar (avsiktligt fail closed) — det ska vara ett medvetet driftval, inte en överraskning.
Ingen merge har utförts av agenten. Ingen produktionspåstående utan deploy-SHA + positiva/negativa HTTP-tester efter release.
## Säkerhetsgranskning (P0) — 2026-11-07
**Verdikt på första revisionen (849e7ae): BLOCKERAD.** Klientheadern var behörighetskälla.
### Vad som var fel
1. `req.headers.get('x-0pt-internal-tenant') ?? process.env.INTERNAL_TENANT_ID` kopierades rakt in i `x-0pt-verified-tenant`. Vem som helst med den delade `X-Internal-Token` kunde därmed välja tenant i en request-header — samma cross-tenant-skrivning som fixen skulle stänga, bara via ett annat fält. `POST canonical/facts tenant_id=999999999999999999` hade fortfarande gett 201.
2. `X-0pt-Internal-Tenant` är klientkontrollerad och därmed inte en behörighetskälla (krav 2 och 4).
3. `INTERNAL_TENANT_ID` som global fallback band inte tjänsten till en verifierad identitet eller ett per-route-scope; en enda miljövariabel blev "alla tenants".
4. Internal-pathen skickade vidare `req.headers` orörda — en klient-konstruerad `x-0pt-verified-tenant`/`x-0pt-verified-sub` kunde passera på alla vägar som inte skrev över dem (bl.a. dev-läget).
5. `POST /api/0pt/quality/bridge-run` tar `tenant_id` ur body och auto-provisionerar tenant-scopade regler — den saknade helt tenant-guard.
### Rättad implementation (b97ad0b, samma PR)
- **`INTERNAL_SERVICE_TENANTS`** (server-env, kommaseparerad allowlist) är nu enda källan till tenant-auktoritet för `X-Internal-Token`-principalen. Tom/osatt ⇒ ingen tenant-auktoritet ⇒ tenant-scopade skrivningar nekas med **403** (fail closed). Tenant-lösa maskinrutter (evidence-outbox/dispatch, er/candidates, search/definitions) påverkas inte.
- **Middleware strippar** inkommande `x-0pt-verified-tenant`, `x-0pt-verified-sub` och `x-0pt-verified-service-tenants` på **alla** vägar (intern token, session, Bearer, dev-läge) innan den sätter dem själv.
- **`x-0pt-verified-service-tenants`** bär allowlisten; `assertTenantMatchesVerified` kontrollerar medlemskap och nekar allt utanför scope. En (1) allowlistad tenant sätts även som `x-0pt-verified-tenant` så befintliga single-tenant-kontroller fortsätter fungera.
- **Bridge-run** fick samma guard som canonical/facts, före `ensureBaselineRules`.
- `X-0pt-Internal-Tenant` är uttryckligen **inte** ett kontrakt och läses aldrig.
- 16 nya regressionstester (`tests/auth/internal-token-tenant-scope.test.ts`): förfalskad header ignoreras/stripas, tom scope ⇒ 403, cross-tenant ⇒ 403, inom scope ⇒ OK, Bearer/dev oförändrat.
- Sviter: `tests/auth` + `0pt-cross-tenant-routes` + `canonical-facts` + `quality-bridge` = **87/87 gröna**; `tsc -p tsconfig.check.json --noEmit` ren.
### Krav 1–10
| Krav | Status |
|---|---|
| 1. Tenant från verifierad serverkontext | ✅ server-env allowlist, aldrig request-fält |
| 2. Klientstyrd tenant kan inte höja rättigheter | ✅ medlemskapstest, annars 403 |
| 3. Interna anrop begränsade till tillåtna tenants/resurser | ✅ `INTERNAL_SERVICE_TENANTS` + ruttlista |
| 4. Förfalskade verifieringsheaders avvisas | ✅ strippas på alla vägar |
| 5. Cross-tenant-test ⇒ 403 | ✅ |
| 6. Samma-tenant-test ⇒ OK | ✅ |
| 7. Befintlig ingestion fungerar | ✅ tenant-lösa rutter opåverkade; canonical kräver allowlist |
| 8. RLS + API-auktorisation tillsammans | ⏳ RLS-test kräver DB-miljö (utanför denna PR) |
| 9. Inga utvecklingsundantag i prod | ✅ `assertAuthConfigSafe` orörd; strip även i dev |
| 10. Gitea-CI + säkerhetskontroller | ⏳ körs på b97ad0b |
### Rekommendation
**Merge först efter ägargodkännande, och sätt `INTERNAL_SERVICE_TENANTS=379976258701823235` (t_0pt) i prod-secreten innan deploy.** Utan den variabeln nekar den interna principalen tenant-scopade skrivningar (avsiktligt fail closed) — det ska vara ett medvetet driftval, inte en överraskning.
Ingen merge har utförts av agenten. Ingen produktionspåstående utan deploy-SHA + positiva/negativa HTTP-tester efter release.
CI: körningar 422444–422447 är köade/pågående på b97ad0b (runners upptagna). Ingen merge utförd — väntar på ägargodkännande och grön CI.
Driftföreberedelse: sätt INTERNAL_SERVICE_TENANTS=379976258701823235 (t_0pt) i 0pt-prod innan deploy. Utan den nekar den interna principalen tenant-scopade skrivningar (avsiktligt fail-closed).
### Uppföljning — rättad implementation pushad (`b97ad0b`)
Den blockerade klientheader-varianten är ersatt. Sammanfattning av läget nu:
**Vad som ändrats**
- `INTERNAL_SERVICE_TENANTS` (server-env) är enda tenant-auktoriteten för `X-Internal-Token`. Tom lista ⇒ ingen auktoritet ⇒ tenant-scopade skrivningar 403.
- Middleware strippar inkommande `x-0pt-verified-tenant`, `x-0pt-verified-sub` och `x-0pt-verified-service-tenants` på **alla** vägar innan den sätter dem själv.
- `assertTenantMatchesVerified` kontrollerar medlemskap i service-scope och failar stängt på tom scope.
- `POST /api/0pt/quality/bridge-run` fick samma guard (saknades helt).
- `X-0pt-Internal-Tenant` läses aldrig och är inte ett kontrakt.
**Verifierat lokalt**
- Ny svit `tests/auth/internal-token-tenant-scope.test.ts`: 16 tester (förfalskad header ignoreras/stripas, tom scope 403, cross-tenant 403, inom scope OK, Bearer/dev oförändrat).
- `tests/auth` + `0pt-cross-tenant-routes` + `canonical-facts` + `quality-bridge` = **87/87**.
- `tsc -p tsconfig.check.json --noEmit` ren.
**CI**: körningar 422444–422447 är köade/pågående på `b97ad0b` (runners upptagna). Ingen merge utförd — väntar på ägargodkännande och grön CI.
**Driftföreberedelse**: sätt `INTERNAL_SERVICE_TENANTS=379976258701823235` (t_0pt) i 0pt-prod innan deploy. Utan den nekar den interna principalen tenant-scopade skrivningar (avsiktligt fail-closed).
Den delade X-Internal-Token identifierar EN tjänsteprocess, inte flera
oberoende principaler. En allowlist med flera tenants skulle låta varje
tokeninnehavare välja mellan dem — samma klientstyrda auktoritet som guarden
ska ta bort. Multi-tenant-tjänsteåtkomst kräver verifierad
workload-identity-binding (CL0UD-delegation); tills den finns failar en
ambig konfiguration stängt (401) och ingen identitet forwardas.
Tester: multi-tenant nekas utan side effect, en tenant fungerar, 75/75 i
auth+ cross-tenant-sviterna, tsc ren.
admin
merged commit c91d2aaca6 into main2026-11-07 15:55:12 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
P0 — tenant-isolering för internal-token-anrop
Fynd (2026-11-07):
POST /api/0pt/canonical/factsmedX-Internal-Tokenochtenant_id=999999999999999999gav HTTP 201 och skrev data i en annan tenant.Orsak:
middleware.tsinternal-token-grenen returneradenext()utan att sättax-0pt-verified-tenant.assertTenantMatchesVerifiedblev därför en no-op.Fix: sätter
x-0pt-verified-tenantfrånX-0pt-Internal-Tenant(ellerINTERNAL_TENANT_ID), och varnar när ingen tenant kan verifieras.Bevis: bogus-tenant-data (1 relation, 2 facts, 3 entities) städades bort och verifierades 0 kvar. Efter fix ska samma anrop nekas.
Åtgärd krävs: merge + deploy av 0pt-api. Ingen dataändring i övrigt.
The first revision of this fix copied a CLIENT header (X-0pt-Internal-Tenant) or a single INTERNAL_TENANT_ID into x-0pt-verified-tenant. That is not an authorization source: any holder of the shared internal token could still name any tenant, leaving the original cross-tenant write reachable through a different header. - INTERNAL_SERVICE_TENANTS (server env) is now the only tenant authority for the X-Internal-Token principal; empty => no authority (fail closed, 403). - Middleware strips inbound x-0pt-verified-{tenant,sub} and the service scope header on every path, including the dev escape hatch. - x-0pt-verified-service-tenants carries the allowlist; the tenant guard checks membership and denies anything outside it. - POST /api/0pt/quality/bridge-run now runs the same tenant guard as canonical/facts (it auto-provisions tenant-scoped rules). - 16 new regression tests; auth + cross-tenant + canonical + bridge suites 87/87; tsc --noEmit clean.Säkerhetsgranskning (P0) — 2026-11-07
Verdikt på första revisionen (
849e7ae): BLOCKERAD. Klientheadern var behörighetskälla.Vad som var fel
req.headers.get('x-0pt-internal-tenant') ?? process.env.INTERNAL_TENANT_IDkopierades rakt in ix-0pt-verified-tenant. Vem som helst med den deladeX-Internal-Tokenkunde därmed välja tenant i en request-header — samma cross-tenant-skrivning som fixen skulle stänga, bara via ett annat fält.POST canonical/facts tenant_id=999999999999999999hade fortfarande gett 201.X-0pt-Internal-Tenantär klientkontrollerad och därmed inte en behörighetskälla (krav 2 och 4).INTERNAL_TENANT_IDsom global fallback band inte tjänsten till en verifierad identitet eller ett per-route-scope; en enda miljövariabel blev "alla tenants".req.headersorörda — en klient-konstrueradx-0pt-verified-tenant/x-0pt-verified-subkunde passera på alla vägar som inte skrev över dem (bl.a. dev-läget).POST /api/0pt/quality/bridge-runtartenant_idur body och auto-provisionerar tenant-scopade regler — den saknade helt tenant-guard.Rättad implementation (
b97ad0b, samma PR)INTERNAL_SERVICE_TENANTS(server-env, kommaseparerad allowlist) är nu enda källan till tenant-auktoritet förX-Internal-Token-principalen. Tom/osatt ⇒ ingen tenant-auktoritet ⇒ tenant-scopade skrivningar nekas med 403 (fail closed). Tenant-lösa maskinrutter (evidence-outbox/dispatch, er/candidates, search/definitions) påverkas inte.x-0pt-verified-tenant,x-0pt-verified-subochx-0pt-verified-service-tenantspå alla vägar (intern token, session, Bearer, dev-läge) innan den sätter dem själv.x-0pt-verified-service-tenantsbär allowlisten;assertTenantMatchesVerifiedkontrollerar medlemskap och nekar allt utanför scope. En (1) allowlistad tenant sätts även somx-0pt-verified-tenantså befintliga single-tenant-kontroller fortsätter fungera.ensureBaselineRules.X-0pt-Internal-Tenantär uttryckligen inte ett kontrakt och läses aldrig.tests/auth/internal-token-tenant-scope.test.ts): förfalskad header ignoreras/stripas, tom scope ⇒ 403, cross-tenant ⇒ 403, inom scope ⇒ OK, Bearer/dev oförändrat.tests/auth+0pt-cross-tenant-routes+canonical-facts+quality-bridge= 87/87 gröna;tsc -p tsconfig.check.json --noEmitren.Krav 1–10
INTERNAL_SERVICE_TENANTS+ ruttlistaassertAuthConfigSafeorörd; strip även i devb97ad0bRekommendation
Merge först efter ägargodkännande, och sätt
INTERNAL_SERVICE_TENANTS=379976258701823235(t_0pt) i prod-secreten innan deploy. Utan den variabeln nekar den interna principalen tenant-scopade skrivningar (avsiktligt fail closed) — det ska vara ett medvetet driftval, inte en överraskning.Ingen merge har utförts av agenten. Ingen produktionspåstående utan deploy-SHA + positiva/negativa HTTP-tester efter release.
Uppföljning — rättad implementation pushad (
b97ad0b)Den blockerade klientheader-varianten är ersatt. Sammanfattning av läget nu:
Vad som ändrats
INTERNAL_SERVICE_TENANTS(server-env) är enda tenant-auktoriteten förX-Internal-Token. Tom lista ⇒ ingen auktoritet ⇒ tenant-scopade skrivningar 403.x-0pt-verified-tenant,x-0pt-verified-subochx-0pt-verified-service-tenantspå alla vägar innan den sätter dem själv.assertTenantMatchesVerifiedkontrollerar medlemskap i service-scope och failar stängt på tom scope.POST /api/0pt/quality/bridge-runfick samma guard (saknades helt).X-0pt-Internal-Tenantläses aldrig och är inte ett kontrakt.Verifierat lokalt
tests/auth/internal-token-tenant-scope.test.ts: 16 tester (förfalskad header ignoreras/stripas, tom scope 403, cross-tenant 403, inom scope OK, Bearer/dev oförändrat).tests/auth+0pt-cross-tenant-routes+canonical-facts+quality-bridge= 87/87.tsc -p tsconfig.check.json --noEmitren.CI: körningar 422444–422447 är köade/pågående på
b97ad0b(runners upptagna). Ingen merge utförd — väntar på ägargodkännande och grön CI.Driftföreberedelse: sätt
INTERNAL_SERVICE_TENANTS=379976258701823235(t_0pt) i 0pt-prod innan deploy. Utan den nekar den interna principalen tenant-scopade skrivningar (avsiktligt fail-closed).