Tre lesere går gjennom koden før du gjør det

En AI kan lese koden din for feil, hull og uklare navn. Men det som må holde hver gang, skal ikke avhenge av at noen husker å spørre. Her er tre lesere som skills, en sjekk i kode, og hvor grensen går. Del 2 av 4 i Koding med AI.

Magnus Gribbestad 8. oktober 2026 10 min
Figur · tre lesere, én port
Tre lesere går gjennom samme kodeendring En kodeendring på tolv linjer. Tre skannelinjer går nedover den samtidig, én for korrekthet (sirkel), én for sikkerhet (rute) og én for lesbarhet (firkant). Seks funn fester seg i margen. Ett av dem strykes som falsk alarm. Nederst kommer en port med en lås: sjekken for nøkler i koden er en git-hook, der exit-kode 1 stopper commiten. ordre.py +12 + API_NOKKEL = "demo-ikke" + def rabatt(n, pris): + if n > 10: + return pris*n*.9 + return pris * n + def finn(db, kunde): + hentet = "2026-10-08" + sql = f"... '{kunde}'" + return db.execute(sql) + def f2(x): + return [o for o in x + if o[1] > 0] korrekthet sikkerhet lesbarhet hemmelighet grensa feil ubrukt injeksjon uklart navn indeksfeil? falsk alarm funn 6 5 GIT-HOOKexit-kode 1 stopper commiten
  1. En endring på tolv linjer. Den ser ryddig ut, og alle linjene er nye.
  2. Tre lesere går over den samme diffen samtidig: korrekthet, sikkerhet og lesbarhet. Hver ser bare etter sitt.
  3. Funnene fester seg i margen, seks stykker, merket med formen til den som fant dem.
  4. Ett av dem er en falsk alarm, og det stryker du. Leserne foreslår. Du sorterer.
  5. Det som må holde hver gang, som at en nøkkel aldri går inn, er en sjekk i kode. Her er den en git-hook, og exit-kode 1 stopper commiten. Vurderingene over er skills.

En god kodegjennomgang ser etter mange ting på en gang, og det er lett å glemme noen av dem. Tre smale lesere, hver med sin sjekkliste, glemmer mindre. Men ikke alt skal en leser ta seg av.

Dette er del 2 av 4 i serien Koding med AI. Del 1 handler om arbeidsflyten, del 3 om å skrive testene først og del 4 om feilsøking. Her får du tre lesere for kode du kan laste ned, en liten sjekk i Python som faktisk er kjørt, og en sjekkliste for deg som leder. Er du leder, kan du hoppe rett til det du bør kreve av et utviklerteam.

Hva en skill er, på ett minutt

En skill er en tekstfil med et navn, en beskrivelse av når den skal brukes, og instruksjoner. Modellen ser bare navnet og beskrivelsen til oppgaven passer. Da leser den resten. Det er en prompt du slipper å lime inn hver gang, og som hele teamet kan dele.

En skill er likevel bare tekst. Den ber modellen gjøre noe, og modellen gjør det som regel. «Som regel» er greit for en vurdering. Det er ikke greit for en regel som aldri skal brytes.

Sjekk i kode eller leser: hvor grensen går

Spørsmålet som avgjør er enkelt: kan det avgjøres uten skjønn, og må det skje hver gang? Da er det kode. Krever det at noen vurderer, er det en leser.

HvaHvor det hører hjemmeHvorfor
Nøkler og passord i kodenKode: hook eller CIEt mønster, og det skal aldri slippe gjennom
SQL bygd med tekstKode, og sikkerhetsleserenDen enkle formen er et mønster. Varianter krever øyne
Testene bestårKode: CI eller en Stop-hookJa eller nei, hver gang
Gjør koden det spesifikasjonen sier?KorrekthetsleserenKrever at noen leser spesifikasjonen og koden sammen
Kan en innlogget bruker misbruke dette?SikkerhetsleserenKrever kjennskap til hvem som kaller koden
Skjønner neste person dette?LesbarhetsleserenSmak og kontekst
Finnes den nye pakken?BeggeEt skript kan slå opp navnet. Om det er riktig pakke, må noen vurdere

En hook er et skript Claude Code kjører automatisk på bestemte punkter, for eksempel rett før en fil endres eller før den sier seg ferdig. Avslutter skriptet med kode 2, stoppes handlingen, og feilmeldinga går tilbake til Claude. Kode 1 stopper ingenting der. Dokumentasjonen sier rett ut at Claude Code regner kode 1 som en feil som ikke blokkerer, og går videre, selv om 1 er den vanlige feilkoden ellers.

Den samme sjekken kan også ligge i en vanlig git-hook før commit, eller i CI, altså den automatiske kjøringen når koden sendes inn. Der stopper alt som ikke er 0, og den virker uansett hvem som skrev koden. Hvorfor en sjekk i kode slår en linje i en instruks, står i del 1.

En liten demo, kjørt

Fila under er konstruert, med plantede feil: en nøkkel rett i koden, SQL bygd med tekst, en variabel som aldri brukes, og to ting en maskin ikke kan se. Rabatten skal gjelde fra og med 10 stk, men koden sier over 10. Og f2 sier ingenting om hva den gjør.

ordre.py, med de plantede feilene
ordre.py
"""Konstruert eksempel med plantede feil. IKKE BRUK DENNE KODEN.

Hører til gribben.no/tech/losninger/kodegjennomgang-med-skills.html.
Feilene er lagt inn med vilje for å vise hva en sjekk i kode finner, og
hva den ikke kan finne fordi det krever en spesifikasjon eller skjønn.
Nøkkelen er ikke ekte.

Spesifikasjon: kunder får 10 prosent rabatt fra og med 10 stk.
"""
import sqlite3

API_NOKKEL = "demo-ikke-ekte-9f8e7d6c5b4a"


def rabatt(antall, pris):
    if antall > 10:
        return pris * antall * 0.9
    return pris * antall


def finn_ordrer(db: sqlite3.Connection, kunde):
    hentet = "2026-10-08"
    sql = f"SELECT id, sum FROM ordre WHERE kunde = '{kunde}'"
    return db.execute(sql).fetchall()


def f2(x):
    return [o for o in x if o[1] > 0]

Sjekken er rundt hundre linjer Python, bare standardbiblioteket. Den leser koden som et tre med modulen ast, ser etter tre mønstre og avslutter med kode 1 hvis den finner noe. Det stopper en git-hook og et CI-steg. Som Claude Code-hook måtte den avsluttet med 2.

Utskrift · kjørt 9. oktober 2026
$ python sjekk.py ordre.py
ordre.py:12   SIKKERHET   hemmelighet i koden: API_NOKKEL
ordre.py:22   OPPRYDDING  «hentet» får en verdi og brukes aldri
ordre.py:23   SIKKERHET   SQL bygd med tekst, bruk parametre (?)
3 funn.
(exit-kode 1)

Alle tre fanget. Så retter vi bare de tre, og kjører igjen:

Utskrift · kjørt 9. oktober 2026
$ python sjekk.py ordre_mekanisk_rettet.py
Ingen funn. Det betyr bare at disse tre tingene ikke ble funnet.
(exit-kode 0)

Ingen funn. Og rabattfeilen står der fortsatt. En kunde som kjøper nøyaktig 10 stk, får ikke rabatten hun har krav på. Ingen mønstersjekk kunne funnet det, for feilen ligger mellom koden og en setning på norsk. Det er jobben til en leser.

ordre_mekanisk_rettet.py, med bare de tre funnene rettet
ordre_mekanisk_rettet.py
"""Samme fil som ordre.py, med bare de tre mekaniske funnene rettet.

Hører til gribben.no/tech/losninger/kodegjennomgang-med-skills.html.
sjekk.py finner ingenting her. Rabattfeilen og det uklare navnet står
fortsatt. Det er derfor «ingen funn» ikke er det samme som godkjent.

Spesifikasjon: kunder får 10 prosent rabatt fra og med 10 stk.
"""
import os
import sqlite3

API_NOKKEL = os.environ.get("API_NOKKEL", "")


def rabatt(antall, pris):
    if antall > 10:
        return pris * antall * 0.9
    return pris * antall


def finn_ordrer(db: sqlite3.Connection, kunde):
    sql = "SELECT id, sum FROM ordre WHERE kunde = ?"
    return db.execute(sql, (kunde,)).fetchall()


def f2(x):
    return [o for o in x if o[1] > 0]

Alle filene ligger i tech/losninger/kode/gjennomgang_demo/: ordre.py, ordre_mekanisk_rettet.py, sjekk.py og pre-commit.txt.

Så leserne: dette fant de

Ved en testkjøring 8. oktober 2026, under arbeidet med sida, fikk de tre skillene den samme rettede fila, hver i en egen Claude-økt uten noe annet å gå på. Svarene under er forkortet og gjenfortalt, med sitat der det står anførselstegn. Kjøringen er ikke gjentatt, de fullstendige svarene er ikke lagret, og svarene blir ikke like neste gang. Les dem som et eksempel på hva slike lesere kan finne, ikke som en måling.

Korrekthet

Fant rabattfeilen og merket den må rettes: rabatt(10, 100) gir 1000, «Svaret skulle vært 900». Foreslo en test for 9, 10 og 11 stk. Pekte på flyttall for pengebeløp som verdt å vurdere, og skrev selv at det var en antakelse fordi koden ikke var kjørt. Kunne ikke si om f2 var riktig, fordi den mangler spesifikasjon.

Skill

Sikkerhet

Ingen tilgangskontroll på kundeoppslaget: kommer kundenummeret rett fra en forespørsel, kan hvem som helst lese andres ordrer. Merket bør rettes, «Middels sikker, siden kallstedet ikke er med». Nøkkelen faller stille tilbake til tom tekst hvis den mangler. Bekreftet at SQL-en nå er parametrisert, og sendte rabattfeilen videre til korrekthetsleseren.

Skill

Lesbarhet

f2(x) sier ingenting. rabatt gir totalprisen, ikke rabatten, og navnet lurer leseren. Tallene 10 og 0.9 mangler navn, og med navn på dem «ser du med en gang at > og fra og med ikke sier det samme». API_NOKKEL brukes aldri.

Skill

Én ting som ser ut som en overlapp, men ikke er det: lesbarhetsleseren kom også innom rabattgrensa. Den sendte selve feilen videre til korrekthetsleseren, men la til det den andre ikke sa: grunnen til at feilen var lett å gjøre. Tall uten navn. Det er derfor tre smale lesere er bedre enn én bred.

Så sorterer du. Rabattfeilen må rettes. Tilgangskontrollen avhenger av hvem som kaller funksjonen, og det vet du, ikke leseren. Flyttall for penger er et valg. Navnene koster fem minutter. Anthropic advarer selv om at en leser som blir bedt om å finne hull, som regel finner noen, også når arbeidet er godt. Derfor ber skillene om få funn med alvorsgrad, og om å si hva de så etter når de ikke fant noe.

/security-review og gjennomgang i GitHub

Claude Code har to innebygde gjennomganger. /security-review kom 6. august 2025 og ser på endringene i grenen din mot standardgrenen på origin, etter blant annet injeksjon, svak innlogging og data på avveie. Den trenger at prosjektet har en origin. /code-review ser etter feil i logikken i diffen. Anthropic har også en GitHub-handling som legger sikkerhetskommentarer direkte i en pull request.

To forbehold fra Anthropic selv er verdt å ta med. Hjelpesenteret skriver at de automatiske gjennomgangene skal komme i tillegg til, ikke i stedet for, sikkerhetsrutinene og kodegjennomgangen dere har fra før. Og GitHub-handlingen er ifølge sin egen beskrivelse ikke sikret mot instruksjoner skjult i koden den leser, så den skal bare brukes på pull requests fra folk dere stoler på. Den filtrerer også bort enkelte typer funn som standard, som tjenestenekt og manglende begrensning av antall forespørsler.

Ingen funn er ikke en godkjenning

En gjennomgang som ikke fant noe, sier bare at den ikke fant det den så etter. Studien til Perry og kollegaene, som står i del 1, viser hvorfor det er farlig: de som brukte AI-assistent, skrev oftere usikker kode og trodde oftere at den var trygg. En tom liste fra en gjennomgang gjør den selvtilliten bare større.

Den andre fella er nyere. Spracklen og kollegaene genererte 576 000 kodeeksempler med 16 språkmodeller og fant at modellene foreslo pakker som ikke finnes, i snitt minst 5,2 prosent av gangene for kommersielle modeller og 21,7 prosent for åpne. Til sammen 205 474 ulike pakkenavn som ikke fantes. Et slikt navn kan hvem som helst registrere og fylle med skadelig kode. Det kalles «slopsquatting». Hver ny pakke må sjekkes av noen som vet hva som var ment.

Hva ledere bør kreve av et utviklerteam

Du trenger ikke lese koden selv. Du trenger at disse ni tingene er sanne, og at noen kan vise deg det. Lista samler lederspørsmålene fra alle fire delene i serien.

Mer om lederens rolle står i AI for ledere.

Tre skills du kan laste ned

kodegjennomgang-korrekthet

Finner spesifikasjonen først, og ser etter grenseverdier, feilhåndtering, tall, tid og tester som er endret for å passe koden. Last ned SKILL.md

Skill

kodegjennomgang-sikkerhet

Leser som en angriper: hemmeligheter, injeksjon, tilgang, personopplysninger og pakker som ikke finnes. Sier fra når et funn heller burde vært en sjekk i kode. Last ned SKILL.md

Skill

kodegjennomgang-lesbarhet

Leser som den neste som skal endre koden: navn, størrelse, kommentarer, magiske verdier og prosjektets egne konvensjoner. Høyst fem funn. Last ned SKILL.md

Skill

Les en skill før du tar den i bruk

En skill er instrukser, og noen har med skript eller gir seg selv tilgang til verktøy. Dokumentasjonen ber deg se over hva skills i et repo får lov til før du kjører Claude Code der. Disse tre er rene tekstfiler uten skript. Les dem likevel, og endre det som ikke passer.

Slik legger du dem inn i Claude Code eller claude.ai: se skillhylla. Vil du bare prøve, åpne en fulltekst under, trykk Kopier, og lim den inn som første melding før koden. Det virker i hvilken som helst chat.

kodegjennomgang-korrekthet i fulltekst
Skill · kodegjennomgang-korrekthet
---
name: kodegjennomgang-korrekthet
description: Leser en kodeendring og sjekker at den gjør det den skal: grenseverdier, feilhåndtering og tester. Bruk ved review av en diff eller pull request, eller før en commit.
---

# Kodegjennomgang: korrekthet

Du er én av tre lesere. Din jobb er å finne ut om koden gjør det den skal. Sikkerhet og lesbarhet har egne lesere, så hold deg til korrekthet. Brukeren eier koden og bestemmer hva som rettes.

Les koden, diffen og testene. Ikke endre filer, og ikke kjør noe som endrer noe. Testene kan du kjøre hvis brukeren ber om det.

## 1. Finn ut hva koden skal gjøre

Les først det som sier hva endringen skal oppnå: oppgaven, en spesifikasjon, kommentarer, testene eller commit-meldingen. Skriv én setning om hva du forstår at endringen skal gjøre.

Finner du ingen spesifikasjon, si det rett ut, og spør brukeren før du fortsetter. Uten den kan du bare finne krasj, ikke feil.

## 2. Se etter dette

- **Grenseverdier:** null, tom liste, én verdi, den største verdien, negative tall. Sammenlign `<` og `<=` mot det spesifikasjonen sier, ord for ord. «Fra og med» og «over» er ikke det samme.
- **Feilhåndtering:** unntak som lekker ut med feil type, feil som svelges stille, returverdier som kan være `None` uten at noen sjekker.
- **Tall:** flyttall for penger, avrunding som gjøres i feil rekkefølge, heltallsdivisjon, enheter som blandes.
- **Tid og tekst:** tidssoner, datoformat, tegnkoding, store og små bokstaver.
- **Tilstand:** verdier som endres et sted og leses et annet, rekkefølge som betyr noe, kode som kjøres to ganger.
- **Tester:** dekker testene det som ble endret? Er det en test som ville feilet før endringen? Har noen endret en test slik at den passer koden i stedet for spesifikasjonen? Si fra med en gang hvis du ser det.

## 3. Svar slik

For hvert funn:

1. Fil og linje, og et kort sitat fra koden.
2. Hva som er feil, sett opp mot spesifikasjonen.
3. Et konkret eksempel på inndata som gir feil svar, og hva svaret burde vært.
4. En test som ville fanget det, skrevet ut.
5. Alvor: **må rettes**, **bør rettes** eller **verdt å vurdere**.

Sorter etter alvor. Ta med høyst sju funn. Har du flere, si hvor mange og hvor de ligger.

## Husk

- Rapporter bare det som påvirker om koden gjør det den skal. Stil og navn er ikke din jobb.
- Ikke finn feil for å se grundig ut. Er en del av koden riktig, si det.
- Skill det du har sjekket fra det du antar. Har du ikke kjørt koden, skriv det.
- Fant du ingenting, skriv hva du så etter. Ingen funn er ikke en godkjenning, og brukeren skal ikke lese det slik.
- Ikke skriv om koden. Foreslå endringen, og la brukeren gjøre den.
kodegjennomgang-sikkerhet i fulltekst
Skill · kodegjennomgang-sikkerhet
---
name: kodegjennomgang-sikkerhet
description: Leser en kodeendring som en angriper: hemmeligheter i koden, injeksjon, svak tilgangskontroll og pakker som ikke finnes. Bruk ved sikkerhetsgjennomgang av en diff eller pull request.
---

# Kodegjennomgang: sikkerhet

Du er én av tre lesere. Din jobb er å lese endringen slik en angriper ville gjort, og finne det som kan misbrukes. Korrekthet og lesbarhet har egne lesere. Brukeren eier koden og bestemmer hva som rettes.

Les koden og diffen. Ikke endre filer, ikke installer noe, og ikke send noe ut av maskinen. Gjennomgangen din erstatter ikke en sikkerhetsskanner, en sjekk for hemmeligheter eller en gjennomgang gjort av et menneske som kan faget.

## 1. Forstå hva som står på spill

Skriv én setning om hva endringen gjør, og hvilke data og hvilke brukere den rører. Spør hvis du ikke vet om koden kjører på en server, hos en bruker eller bare lokalt. Det endrer hva som er alvorlig.

## 2. Se etter dette

- **Hemmeligheter:** nøkler, passord og tokens i koden, i konfigurasjon som sjekkes inn, i testdata og i logger.
- **Injeksjon:** SQL bygd med tekst, kommandoer med `shell=True` eller tekst limt inn i en kommando, filstier fra brukeren (`../`), HTML som ikke escapes, `pickle` eller `yaml.load` på data utenfra.
- **Tilgang:** kan hvem som helst kalle dette? Sjekkes det at brukeren eier det den ber om? Er standardvalget åpent eller lukket?
- **Avhengigheter:** for hver ny pakke, sjekk at den finnes i det offisielle registeret, at navnet er stavet riktig, og at det er den pakken som var ment. Kodemodeller foreslår pakker som ikke finnes, og noen registrerer slike navn med skadelig innhold. Se også etter versjoner som ikke er låst.
- **Personopplysninger:** navn, e-post, telefon, fødselsnummer eller helse i logger, feilmeldinger eller analyse. Logges mer enn nødvendig?
- **Tekst til en språkmodell:** sendes data utenfra rett inn i en prompt, kan det inneholde instruksjoner. Kan modellen da gjøre noe den ikke burde?

## 3. Svar slik

For hvert funn:

1. Fil og linje, og et kort sitat.
2. Hvordan det kan misbrukes, i én eller to setninger. Konkret, ikke dramatisk.
3. Hva som må til for å rette det.
4. Alvor: **må rettes før commit**, **bør rettes** eller **verdt å vurdere**, og hvor sikker du er.
5. Om funnet heller burde vært en sjekk i kode som kjøres hver gang (en hook, et CI-steg eller en skanner), fordi det ikke krever skjønn.

Sorter etter alvor. Ta med høyst sju funn.

## Husk

- Ikke rapporter teoretiske problemer uten en vei inn. Si hva en angriper må ha tilgang til.
- Er du usikker på om en pakke finnes, skriv at den må sjekkes. Ikke gjett.
- Gjenta aldri en hemmelighet du finner i svaret ditt. Skriv hvor den står.
- Fant du ingenting, skriv hva du så etter. Ingen funn er ikke en godkjenning, og en AI-gjennomgang erstatter ikke en sikkerhetsgjennomgang.
- Er koden fra jobb, minn brukeren på å bruke et verktøy virksomheten har godkjent.
kodegjennomgang-lesbarhet i fulltekst
Skill · kodegjennomgang-lesbarhet
---
name: kodegjennomgang-lesbarhet
description: Leser en kodeendring som den neste som skal endre den: uklare navn, kode som gjør for mye og brudd på prosjektets konvensjoner. Bruk ved review av en diff eller pull request.
---

# Kodegjennomgang: lesbarhet

Du er én av tre lesere. Du leser endringen som den som skal endre koden om et halvt år, uten å ha vært med da den ble skrevet. Korrekthet og sikkerhet har egne lesere. Brukeren eier koden og bestemmer hva som rettes.

Les koden og diffen. Ikke endre filer, og ikke kjør noe.

## 1. Les prosjektets regler først

Se etter `CLAUDE.md`, en stilguide, en linter-konfigurasjon eller tilsvarende. Prosjektets egne konvensjoner går foran din smak. Det som en linter eller formaterer allerede sjekker, skal du ikke gjenta. Si heller fra hvis den ikke kjøres.

## 2. Se etter dette

- **Navn:** sier navnet på en funksjon eller variabel hva den er eller gjør? `f2(x)` gjør ikke det.
- **Størrelse:** gjør en funksjon mer enn én ting? Må du holde mange ting i hodet samtidig for å lese den?
- **Kommentarer:** forklarer de hvorfor koden er slik, eller gjentar de bare hva den gjør? Mangler det en forklaring der et valg ikke er opplagt?
- **Magiske verdier:** tall og tekst uten navn, som `0.9` eller `"2026-10-08"`, der leseren må gjette hva de betyr.
- **Død kode:** variabler, parametre, importer og grener som aldri brukes.
- **Konsistens:** gjør den nye koden ting på samme måte som resten av prosjektet?

## 3. Svar slik

Ta med høyst fem funn, de som gjør mest for den neste leseren. For hvert funn:

1. Fil og linje, og et kort sitat.
2. Hva som er vanskelig å forstå, og for hvem.
3. Et konkret forslag, for eksempel et bedre navn.

Avslutt med én setning om hva som er bra med endringen, hvis noe er det.

## Husk

- Smak er ikke et funn. Er det bare en annen måte å skrive det på, la det ligge.
- Ikke skriv om koden. Foreslå, og la brukeren velge.
- Bruk ikke mer av brukerens tid enn funnene er verdt. Fem gode funn er bedre enn tjue små.
- Fant du ingenting som er verdt å endre, si det.
For deg som bygger: sjekken i Python

Kjør den med python sjekk.py fil.py. Filer som ikke slutter på .py, hoppes over, og en fil den ikke klarer å tolke, teller som et funn. Den er bevisst enkel og erstatter ikke en ordentlig skanner for hemmeligheter.

Vil du at den skal stoppe en commit, legg sjekk.py i tools/ og lagre denne fila som .git/hooks/pre-commit, uten filendelse. Den sender bare Python-filene i commiten videre. Merk at sjekken leser filene slik de ligger i mappa, ikke slik de er lagt til med git add.

pre-commit.txt
#!/bin/sh
# Git-hook som kjører sjekk.py på Python-filene i commiten.
# Hører til gribben.no/tech/losninger/kodegjennomgang-med-skills.html.
#
# Lagre fila som .git/hooks/pre-commit (uten .txt), og legg sjekk.py i tools/.
# På Mac og Linux må den gjøres kjørbar: chmod +x .git/hooks/pre-commit
# Git stopper commiten når denne avslutter med noe annet enn 0.
#
# Merk: sjekk.py leser filene slik de ligger i arbeidskopien, ikke slik de
# er lagt til med git add. Har du endret en fil etter git add, sjekkes den
# nyeste versjonen. Filnavn med mellomrom støttes ikke.
filer=$(git diff --cached --name-only --diff-filter=ACM -- '*.py')
[ -z "$filer" ] && exit 0
python tools/sjekk.py $filer

Prøvd i et tomt testrepo 9. oktober 2026: commit med ordre.py ble stoppet med de tre funnene over, commit med den rettede fila gikk gjennom.

sjekk.py
"""Mekanisk sjekk av Python-filer. Bare standardbiblioteket.

    python sjekk.py ordre.py [flere filer ...]

Finner tre ting som ikke krever skjønn:
  - hemmeligheter skrevet rett i koden (navn som nøkkel, token, passord)
  - SQL bygd med tekst (f-streng, + eller .format rundt SQL-ord)
  - variabler som får en verdi og aldri brukes

Avslutter med kode 1 hvis den finner noe. I en git-hook før commit og i CI
stopper det, for der stopper alt som ikke er 0. Som Claude Code-hook ville
kode 1 IKKE stoppet noe: der blokkerer bare kode 2, og kode 1 regnes som en
feil som ikke blokkerer (code.claude.com/docs/en/hooks, lest 9. oktober
2026). Skriptet er laget for git og CI.

Det er hele poenget: en regel som MÅ holde, skal ikke avhenge av at noen
husker å be om den. Det som krever skjønn, som om koden gjør det
spesifikasjonen sier, er jobben til skillene.

Filer som ikke slutter på .py, hoppes over. En .py-fil som ikke kan tolkes,
teller som et funn, så en commit ikke slipper gjennom fordi sjekken krasjet.

Sjekken er bevisst enkel og vil både bomme og ta feil. Den erstatter ikke
en ordentlig skanner for hemmeligheter eller en linter. Den viser prinsippet.
"""
import ast
import pathlib
import re
import sys

HEMMELIG_NAVN = re.compile(r"(n[oø]kkel|key|secret|hemmelig|passord|password|token)", re.I)
SQL_ORD = re.compile(r"\b(SELECT|INSERT|UPDATE|DELETE)\b", re.I)


def tekst_i(node) -> str:
    """Den bokstavelige teksten i en streng-node, også delene av en f-streng."""
    return " ".join(n.value for n in ast.walk(node)
                    if isinstance(n, ast.Constant) and isinstance(n.value, str))


def er_bygd_tekst(node) -> bool:
    if isinstance(node, ast.JoinedStr):
        return any(isinstance(v, ast.FormattedValue) for v in node.values)
    if isinstance(node, ast.BinOp) and isinstance(node.op, (ast.Add, ast.Mod)):
        return True
    if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
        return node.func.attr == "format"
    return False


def sjekk_fil(sti: pathlib.Path):
    # utf-8-sig tåler filer som starter med et BOM-merke, slik noen editorer lagrer dem.
    try:
        tre = ast.parse(sti.read_text(encoding="utf-8-sig"), filename=str(sti))
    except (SyntaxError, UnicodeDecodeError, ValueError) as feil:
        return [(getattr(feil, "lineno", None) or 0, "FEIL", f"kunne ikke tolkes: {type(feil).__name__}")]
    funn = []

    for node in ast.walk(tre):
        # Hemmeligheter: et navn som ser hemmelig ut, satt til en lang tekst
        if isinstance(node, ast.Assign) and isinstance(node.value, ast.Constant) \
                and isinstance(node.value.value, str) and len(node.value.value) >= 8:
            for mål in node.targets:
                if isinstance(mål, ast.Name) and HEMMELIG_NAVN.search(mål.id):
                    funn.append((node.lineno, "SIKKERHET", f"hemmelighet i koden: {mål.id}"))

        # SQL bygd med tekst
        if er_bygd_tekst(node) and SQL_ORD.search(tekst_i(node)):
            funn.append((node.lineno, "SIKKERHET", "SQL bygd med tekst, bruk parametre (?)"))

        # Ubrukte variabler, én funksjon om gangen. En indre funksjon blir gått
        # gjennom både alene og som del av den ytre, derfor set() nederst.
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
            satt, brukt = {}, set()
            for n in ast.walk(node):
                if isinstance(n, ast.Name):
                    if isinstance(n.ctx, ast.Store):
                        satt.setdefault(n.id, n.lineno)
                    else:
                        brukt.add(n.id)
            for navn, linje in satt.items():
                if navn not in brukt and not navn.startswith("_"):
                    funn.append((linje, "OPPRYDDING", f"«{navn}» får en verdi og brukes aldri"))

    return sorted(set(funn))


def main(filer) -> int:
    totalt = 0
    for f in filer:
        sti = pathlib.Path(f)
        if sti.suffix != ".py":
            print(f"{sti.name}: hoppet over, ikke en Python-fil")
            continue
        for linje, type_, tekst in sjekk_fil(sti):
            print(f"{sti.name}:{linje:<4} {type_:<11} {tekst}")
            totalt += 1
    print(f"{totalt} funn." if totalt else "Ingen funn. Det betyr bare at disse tre tingene ikke ble funnet.")
    return 1 if totalt else 0


if __name__ == "__main__":
    sys.stdout.reconfigure(encoding="utf-8")
    sys.exit(main(sys.argv[1:]))

Kjørt på nytt med Python 3.13, 9. oktober 2026. Begge utskriftene på sida er de faktiske.

Skillene er nye, og ingen har målt hvor godt de virker utover testkjøringen over. Prøv dem på noe lite først.

Oppdatert oktober 2026. Claude Code-dokumentasjonen, lest 8. oktober 2026: Commands (/security-review sammenligner med standardgrenen på origin og trenger en origin, /code-review; lest på nytt 9. oktober 2026), Hooks og Hooks reference (kode 2 stopper handlingen, kode 1 blokkerer ikke; lest på nytt 9. oktober 2026), Best practices (hooks som garanti, lesere som alltid finner noe), Skills (plassering, se over skills i et repo). Anthropic, «Automate security reviews with Claude Code», 6. august 2025. Claude hjelpesenter, Automated security reviews in Claude Code (oppdatert 16. mars 2026: «complement, not replace») og Creating custom skills. GitHub, anthropics/claude-code-security-review (forbeholdet om skjulte instruksjoner og filtrerte funn). Spracklen m.fl., «We Have a Package for You!», USENIX Security 2025. Lesernes svar er fra én testkjøring 8. oktober 2026 under arbeidet med sida, forkortet, ikke gjentatt, og blir ikke like neste gang. Alt i eksempelet er konstruert, og nøkkelen er ikke ekte.