feat(gate): pakke-gaten leser INNHOLDET, ikke bare filnavn + vurdering av Azure-omdøping

ORDRE 20260818T103716Z-251212929. To deler.

DEL 2 - innholds-gapet (TDD, red-first MÅLT):
Hver eksisterende gate i test_handover_package_loadbearing.py leser arkiv-MEDLEMSNAVN.
Ingen leste hva medlemmene SIER - som er nøyaktig hvorfor ressursgruppe, ressurs,
prosjekt og vertsnavn nådde en ekstern organisasjon i 14:24-bygget uten at én av 869
tester merket det.

RED FIRST, mot ekte data: gate-kroppen kjørt mot den LEVERTE zip-en gir 7 funn
(vertsnavnet + seks /Users-stier), mot `git archive 77076b9` gir den 8. Den finner
altså det som faktisk lakk, før den brukes til å påstå at HEAD er ren.

Gaten er en NEKTELSE, aldri et filter: den fjerner ingenting fra arkivet, den sier at
treet ikke er leveringsklart. Pakka forblir `git archive HEAD` - kø-(p) intakt.
Mønstrene bor i ÉN liste, hver rad med sin egen kjent-positive prøve, og hver prøve er
BYGGET VED KONKATENERING så fila ikke matcher seg selv (verifisert: 0 funn i egen kilde
- ellers hadde eneste fiks vært et hull i gaten der en hemmelighet kan gjemme seg).
Aksept-lista er selv gatet: en oppføring som ikke lenger nås er drift og felles.

MÅLT, seks mutasjoner - fem røde, én uten diskriminerende kraft:
- detach scanneren               -> 1 rød (leaked-kontrollen)
- aksept-lista sluker ekte vert  -> 2 røde
- ødelegg vertsnavn-regexen      -> 2 røde
- foreldet aksept-oppføring      -> 1 rød (minimalitets-kontrollen)
- koordinat tilbake i HEAD       -> 1 rød, gaten ALENE
- fjern nevner-asserten          -> 0 røde (kontroll, ikke søm - uttalt)

Nevner: 325 medlemmer lest, 1 hoppet over (sqlite-binær). 873 passed / 5 skipped.

ÆRLIGHETS-GRENSE, uttalt i koden: gaten fanger STRUKTUR. Vertsnavnet har en form;
ressursgruppe og prosjekt er fri tekst uten form, og ble i august bare oppdaget fordi
de sto i samme tabell som verten. Å lukke det gapet krever en navneliste - den andre
kopien av eksponeringsregelen, som er dét pakkas `git archive HEAD`-form finnes for å
forby.

DEL 1 - vurdering av omdøping (ingenting rørt i Azure):
docs/2026-08-18-vurdering-azure-omdoeping.md. Anbefaling: IKKE døp om. Lekkasjen ga
MÅLRETTING, ikke tilgang, og målrettingen kan ikke trekkes tilbake - navnene ligger i
publisert historikk og i en zip hos en tredjepart. Omdøping finnes dessuten ikke som
operasjon: et custom subdomain KAN IKKE endres (Learn), så det er riving + gjenoppbygging
i sju steg. Det ene tiltaket som faktisk fjerner en autorisasjonsvei Entra ikke dekker er
`disableLocalAuth` + nøkkelregenerering. Beslutningen er operatørens; valgene står med
konsekvenser, ikke som konklusjon.

PREMISS KORRIGERT (Verifiseringsloven ansikt 3): ordren sa koordinatene sto i repoet og
at tre /Users/ktg-stier lå på open/main. Målt på 6d2837f: 0 og 0 - 241b50d og 6d2837f
lukket begge. Premisset var sant da ordren ble skrevet (10:37Z) og sluttet å være det
13:03/13:20. Ingen begrunnet aksept-oppføring var derfor nødvendig for sti-klassen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01964PUr46mfnxWtMw23AnVD
This commit is contained in:
Kjell Tore Guttormsen 2026-08-18 13:42:02 +02:00
commit 4cf8c4f6ba
2 changed files with 404 additions and 0 deletions

View file

@ -0,0 +1,199 @@
# Vurdering: skal Azure-ressursene døpes om?
**Bestilt av** ordre `20260818T103716Z-251212929` (fra `.claude`). **Leveransen er en vurdering, ikke
en omdøping** — ingenting i Azure er rørt. Beslutningen er operatørens.
Alt under er enten **målt** i dette repoet eller **verifisert mot Microsoft Learn**. Der noe ikke er
verifisert, står det. Kildene er listet i §6.
## 1. Hva røper koordinatene faktisk?
Koordinatene som nådde en ekstern organisasjon 14.08 (i `dist/portfolio-optimiser-foundry-1.1.0.zip`,
bygget 14:24):
| Verdi | Type |
|---|---|
| `<resource-group>` | ressursgruppe (ARM-planet) |
| `<resource>` | Foundry-ressurs = **custom subdomain** (DNS) |
| `<project>` | prosjekt (ARM + data-plan-sti) |
| `eastus` | region |
| `https://<resource>.services.ai.azure.com/api/projects/<project>` | prosjekt-endepunkt |
De literale navnene er byttet mot plassholdere her, samme form som `DEPLOY.md`, auth-oppskriften og
måleprotokollen etter `241b50d`. Dokumentet handler om hva navnene *er*, ikke om hvilke de var — og
innholds-gaten i `tests/test_handover_package_loadbearing.py` avviste førsteutkastet som bar dem.
**Utledbart uansett — ikke lekket av oss:**
- **At det er en AIServices-ressurs med prosjekter.** URL-formen `…/api/projects/<project>` ER den
dokumenterte Foundry-prosjekt-endepunkt-formen. Enhver som ser en slik URL vet ressurstypen.
- **Modell, versjon og deployment-navn** (`gpt-4.1-mini`, `2025-04-14`, GlobalStandard). Offentlig
katalog-nomenklatur.
- **Rolle-GUID-en `53ca6127-db72-4b80-b1b0-d745d6d5456d`.** Azures **offentlige** innebygde
role definition id for `Foundry User`, identisk i hver tenant. Den står i Learn-dokumentasjonen.
Den er **ikke** en koordinat, og skal ikke plassholdes.
- **At vertsnavnet i det hele tatt er et globalt, gjettbart navnerom.** Custom subdomain ligger under
ett felles DNS-navnerom, og navnet er unikt på tvers av alle kunder — så *eksistensen* av et gitt
navn kan enhver bekrefte ved å slå det opp. Lekkasjen fjernet **gjettingen**, ikke muligheten.
**Kun kjent fordi navnene lekket:**
- **Ressursgruppenavnet.** Det finnes ikke i DNS og ikke i noen data-plan-URL. Det er rent
ARM-plan-informasjon.
- **Regionen** (`eastus`). Ikke utledbar fra vertsnavnet for `*.services.ai.azure.com`.
- **Prosjektnavnet** (`<project>`) — det står riktignok i endepunkts-URL-en, men URL-en er selv en
del av lekkasjen.
- **Koblingen mellom dem.** Det operativt verdifulle er ikke ett navn, men at ressursgruppe,
ressurs, prosjekt, region og rolle-scope kommer som ett ferdig sett.
**Kort:** dette er rekognoseringsinformasjon om ARM-planet. Det er ikke nøkler, og det er ikke
tenant-id eller abonnements-id — ingen av de to sto i dokumentet (målt: `0` treff på
`/subscriptions/<guid>` i hele treet og i den leverte pakka).
## 2. Hva skal til for å misbruke dem?
### Det Entra faktisk stopper
Et kall mot prosjekt-endepunktet krever **både** et gyldig Entra-token for scopet
`https://ai.azure.com/.default` **og** en RBAC-tildeling på ressurs- eller prosjekt-scope. Microsofts
egen feilkode-tabell skiller de to: **401** = manglende/utløpt token, **403** = manglende
rolletildeling. Å kjenne adressen gir altså i seg selv **null** inferens-tilgang.
Token-basert auth krever dessuten et custom subdomain — regionale endepunkter støtter ikke Entra i
det hele tatt. Vi bruker custom subdomain, altså er Entra-stien tilgjengelig.
### Det Entra ikke stopper
1. **Nøkkelbasert auth, hvis den er på.** Entra blir *eneste* autorisasjonsmetode først når
`disableLocalAuth` er satt til `true` — det er en eksplisitt handling (Azure Policy på
abonnement/ressursgruppe, `disableLocalAuth` i ARM/Bicep, eller `Set-AzCognitiveServicesAccount
-DisableLocalAuth $true`). Er den ikke satt, finnes det nøkler som omgår Entra fullstendig.
**Ikke verifisert for denne ressursen:** `az cognitiveservices account create`-kommandoen i
måleprotokollen (`docs/2026-08-14-fase1b-forste-levende-kjoring.md` §0) ba ikke om det, og om
abonnementet har policyen er ukjent herfra. **Dette er den ene sjekken som faktisk endrer
risikobildet** — se §4.
Merk også at avslåing ikke slår inn momentant: endringen skjer i kontrollplanet med én gang, men
gatewayen kan godta tidligere gyldige nøkler til cachen oppdateres — typisk minutter, opptil
flere timer. Og allerede utdelte nøkler må regenereres separat; å slå av lokal auth *tilbakekaller*
dem ikke.
2. **Målrettet phishing og consent-phishing.** Koordinatene gjør en henvendelse troverdig — avsender
kan navngi ressursgruppe, ressurs og prosjekt riktig. Entra beskytter identiteten, ikke
overtalelsen. Dette er den mest realistiske misbruksveien for denne typen lekkasje.
3. **Kvote- og kostnadsmisbruk ved kompromittert identitet.** Kvote tildeles **per abonnement, per
region, per modell og deployment-type**, i tokens-per-minutt, og deles av alle deployments av
samme modell i samme region i abonnementet. En misbrukt identitet med `Foundry User` på dette
prosjektet spiser altså av en pott som er felles, og kan strupe *andre* deployments av samme
modell i samme abonnement — ikke bare denne. Forbruket faktureres.
4. **Nettverksflaten.** `publicNetworkAccess` er en egenskap som må settes til `Disabled` for å stenge
den offentlige inngangen. Er den ikke det, er endepunktet nåbart fra internett — det var sant før
lekkasjen også. Forskjellen er at adressen nå er *kjent*, ikke at den ble *nåbar*.
**Ikke verifisert:** om et uautentisert kall skiller et eksisterende prosjektnavn fra et
ikke-eksisterende (altså om `<project>` kan bekreftes uten token). Det ville krevd et faktisk kall
mot en fremmed ressurs, og det er ikke gjort.
## 3. Hva koster omdøping?
### I repoet: null
Målt på `6d2837f` (= `open/main` = `origin/main`), nevner **327 sporede filer**:
| Sted | Treff på de tre literale navnene |
|---|---|
| Sporet tre (kode, tester, docs, `env.template`) | **0** |
| Usporet deck `docs/presentasjon-portfolio-optimiser.html` | **0** |
| `shared/` (subtree) | **0** |
De 18 gjenværende linjene med `services.ai.azure.com` er plassholderformen (`<resource>.`),
wildcard-formen (`*.`) eller testdummies (`x.`, `platform.`, `wrong.`). `env.template` bærer
variabelnavn, aldri verdier. **Omdøping krever altså ingen redigering i repoet**`241b50d` gjorde
allerede den jobben.
### I historikken og i den leverte pakka: kan ikke tilbakekalles
- **Git-historikken:** 3 linjer i 1 fil, i 3 commits (`5bd8e1c`, `bb4807a`, `241b50d`). Publisert på
`open/main`. Ordren forbyr å skrive om historikken, og vei B ble alt avvist 18.08.
- **Den leverte pakka:** 4 linjer, hos en tredjepart siden 14.08. En omdøping i Azure gjør ikke det
usett.
### I Azure: en full riving og gjenoppbygging
**Et custom subdomain kan ikke endres.** Microsoft er eksplisitt: navnet kan ikke endres etter at det
er opprettet og knyttet til ressursen, og for å gjenbruke et navn må den eksisterende ressursen
slettes. «Omdøping» finnes derfor ikke som operasjon — det er:
1. opprett ny AIServices-ressurs med nytt subdomain (+ `--allow-project-management`)
2. opprett nytt prosjekt
3. redeploy `gpt-4-1-mini` (ny kvotetildeling i regionen)
4. tildel `Foundry User` på nytt prosjekt-scope
5. oppdater `PORTFOLIO_FOUNDRY_PROJECT_ENDPOINT` og `PORTFOLIO_MODEL_MAP` lokalt
6. slett — og purge — den gamle ressursen (purge krever `Contributor` på abonnements-scope)
7. re-kjør stigen i måleprotokollen §1 for å bevise at auth/RBAC/endepunkt fortsatt komponerer
**Ikke verifisert:** om det gamle subdomain-navnet holdes reservert en periode etter sletting slik
App Service og API Management gjør (anti-subdomain-takeover). Learn dokumenterer den mekanismen for
de tjenestene, men ikke for Foundry-/Cognitive Services-subdomener. Ikke anta at navnet frigis — og
ikke anta at det er låst.
## 4. Anbefaling — og hva hvert valg koster
**Anbefalingen er: ikke døp om. Fjern i stedet den ene veien som ikke går gjennom Entra, og gjør
misbruk synlig.**
Begrunnelsen er at omdøping løser feil problem. Det lekkasjen ga en motpart er **målretting**, ikke
**tilgang**. Og målrettingen kan ikke trekkes tilbake: navnene ligger i publisert git-historikk og i
en zip hos en tredjepart. En omdøping ville altså kjøpt at *dagens* ressurs ikke er den som ble
navngitt — ikke at navngivingen forsvinner. Det er en reell, men liten gevinst, og den betales med en
full riving av det eneste levende Foundry-oppsettet prosjektet har.
**Valg A — behold navnene, herd oppsettet (anbefalt).**
- Sjekk `DisableLocalAuth` på ressursen. Er den ikke `true`: regenerer begge nøklene *og* sett den.
Dette er den eneste tiltaket som fjerner en autorisasjonsvei Entra ikke dekker.
- Sjekk at rolletildelingen står på **prosjekt**-scope og ikke bredere, og vurder
`Foundry Agent Consumer` framfor `Foundry User` dersom kjøringene bare gjør inferens.
- Sett et kostnadsvarsel på abonnementet og hold TPM-tildelingen på deploymentet lav. Kvotemisbruk
blir da både begrenset og synlig.
- *Konsekvens:* måleprotokollen forblir reproduserbar, ingen ny kjøring må betales, og navnene
fortsetter å stå i historikken — som de ville gjort uansett.
**Valg B — riv og bygg opp igjen med et intetsigende navn.**
- *Kjøper:* at et navn en motpart eventuelt sitter og venter på, ikke lenger peker på noe levende.
- *Koster:* de sju stegene i §3, ny betalt verifiseringskjøring, og at
`docs/2026-08-14-fase1b-forste-levende-kjoring.md` beskriver et oppsett som ikke finnes lenger.
- *Kjøper ikke:* at navnene forsvinner fra historikken eller fra den leverte pakka.
- Velg denne hvis vurderingen er at ressursgruppe- og prosjektnavnet i seg selv er sensitivt i
organisasjonssammenheng — det er en vurdering operatøren kan gjøre og ikke jeg.
**Valg C — gjør begge.** Herdingen i A er verdt å gjøre *uansett* hvilket av A og B som velges; B
uten A etterlater den samme nøkkel-veien åpen på en ny ressurs.
## 5. Hva denne vurderingen ikke dekker
- Ingenting i Azure er inspisert. Alle utsagn om *denne* ressursens faktiske konfigurasjon
(`disableLocalAuth`, `publicNetworkAccess`, rolle-scope) er markert som uverifiserte over.
- **Innholds-gaten fanger vertsnavnet, ikke ressursgruppe- og prosjektnavnet.** Et vertsnavn har en
struktur (`<label>.services.ai.azure.com`); en ressursgruppe heter hva som helst. De to andre
navnene ble bare oppdaget fordi de sto i samme tabell som verten. En gate kan ikke lukke det
gapet uten en navneliste, og en navneliste er den andre kopien av eksponeringsregelen.
- Ordre-teksten sa at koordinatene fortsatt sto i repoet. Det er **ikke** tilfelle per `6d2837f`
målt, se §3. Premisset var riktig da ordren ble skrevet (10:37Z) og sluttet å være det 13:03.
## 6. Kilder
Alle verifisert 18.08.2026 mot Microsoft Learn:
- Custom subdomain kan ikke endres; må slette ressursen for å gjenbruke navnet —
`learn.microsoft.com/azure/ai-services/cognitive-services-custom-subdomains`
- Entra-auth krever custom subdomain; 401 vs 403; scope `https://ai.azure.com/.default`
`learn.microsoft.com/azure/foundry/concepts/authentication-authorization-foundry`
- `disableLocalAuth` er en eksplisitt handling; propagering minutter til timer; nøkler må regenereres
separat — `learn.microsoft.com/azure/ai-services/disable-local-auth`
- Rolle-id `53ca6127-db72-4b80-b1b0-d745d6d5456d` = `Foundry User`, offentlig og lik i hver tenant;
`Foundry Agent Consumer` som minste-privilegium for ren inferens —
`learn.microsoft.com/azure/foundry/concepts/rbac-foundry`
- Kvote per abonnement/region/modell/deployment-type i TPM; deles av deployments i samme region —
`learn.microsoft.com/azure/foundry/openai/how-to/quota`
- `publicNetworkAccess` / `disableLocalAuth` som ARM-egenskaper —
`learn.microsoft.com/azure/templates/microsoft.cognitiveservices/accounts`
- Anti-subdomain-takeover-reservasjon (dokumentert for App Service / API Management, **ikke** for
Cognitive Services) — `learn.microsoft.com/azure/security/fundamentals/subdomain-takeover`