Syntaks er reglene for hvordan kode må skrives for at en datamaskin skal kunne lese og tolke innholdet. Syntaksen danner grunnlaget for semantikken, som er hva koden faktisk utfører, og pragmatikken, som er om koden gjør det riktig i sammenhengen den brukes i. Et program er kun sikkert når alle tre er oppfylt, og denne artikkelen handler om hva som skjer når ett av dem mangler, og hvor store konsekvenser det kan få.
Syntaks
Alle språk, både menneskelige og maskinelle, har en form og en betydning. Setningen «Bilen kjører på veien» følger norsk grammatikk. «Lastebilen kjører på veien» følger de samme reglene, men sier noe annet. «Lastebilen kjørte på veien» har plutselig en annen betydning.
Syntaks er navnet på formen, altså reglene for hvordan tegn og ord kan settes sammen. Ordet kommer fra gresk syntaxis, som betyr «sammenstilling». Filosofen Charles Morris delte i 1938 studiet av tegn i tre lag: syntaks, semantikk og pragmatikk. Denne filosofiske tredelingen brukes også om programmeringsspråk.
Slik leser maskinen koden
Et programmeringsspråk er et formelt språk. Reglene er skrevet ned helt presist, og de kalles språkets grammatikk. Når en datamaskin leser kode, skjer det trinn for trinn:
- En lekser deler teksten opp i minste enheter, for eksempel
req,.oguser. - En parser sjekker om enhetene står i en rekkefølge grammatikken tillater.
- Er rekkefølgen gyldig, bygger parseren et syntakstre, en strukturert modell av koden.
- Er rekkefølgen ugyldig, får utvikleren en syntaksfeil, og koden kjører ikke.
Syntaksen er altså alt parseren sjekker, og ingenting mer. Den som ikke kan syntaksen, kan heller ikke lese hva koden gjør. Derfor er syntaksen grunnlaget de to andre lagene bygger på.
Semantikk
Semantikk er hva koden betyr, altså hva den faktisk gjør når den kjører. To linjer kan ha nesten lik syntaks og likevel gjøre helt forskjellige ting. Den som bare kjenner formen, ser ingen forskjell.
Et token og en signatur
Når en bruker logger inn på en nettjeneste, får brukeren ofte et token. Et token er et stykke data som viser at noen har fått tilgang. Et vanlig format er JWT (JSON Web Token). Det består av et innhold med opplysninger om brukeren og en kryptografisk signatur.
Signaturen er laget med en hemmelig nøkkel som bare den som utsteder tokenet har, og den viser at innholdet ikke er endret. JWT brukes ofte til autentisering, altså til å fastslå hvem brukeren er. Poenget med signaturen er at systemet selv kan sjekke om tokenet er ekte.
Her er to linjer skrevet med biblioteket jsonwebtoken for Node.js:
const innhold = jwt.decode(token);const verifisertToken = jwt.decode(token);
Hvilken av dem verifiserer tokenet? Ingen av dem. Begge gjør det samme: de leser innholdet uten å sjekke signaturen. Variabelnavnet i den andre linjen lover verifisering, men funksjonen til høyre gjør det ikke. Maskinen bryr seg ikke om navnet, det er bare mennesker som leser det.
En linje som faktisk verifiserer, ser slik ut:
const bruker = jwt.verify(token, offentligNokkel, { algorithms: ['RS256'] });
RS256 er signeringsmetoden. Utstederen signerer med sin hemmelige nøkkel, og alle andre sjekker signaturen med en tilhørende offentlig nøkkel. verify leser også innholdet, men først etter at signaturen er sjekket. Med decode kan hvem som helst lage sitt eget token og utgi seg for å være en annen.
Forskjellen ligger i ett funksjonsnavn. Stegene som skiller de to funksjonene, er skjult inne i biblioteket. Den som leser koden, må derfor vite hva funksjonen gjør, ikke bare hva den heter.
Pragmatikk
Pragmatikk er om koden gjør det riktige i sammenhengen den brukes i. Her handler det om hvem som står bak dataene, og hvor mye tillit de fortjener. Pragmatikk er mindre presist definert i programmering enn syntaks og semantikk. I denne artikkelen betyr det forholdet mellom koden, dataene og menneskene rundt.
Hvem styrer dataene?
Når en bruker sender noe til en nettjeneste, skjer det gjennom en HTTP-forespørsel. En slik forespørsel er tekst som brukerens maskin lager og sender. Alt i den er under brukerens kontroll, også tokenet og innholdet (body).
I et program skrevet med Express for Node.js skjer dette trinn for trinn:
- Forespørselen kommer inn, og innholdet blir lagt i
req.body. Det er data brukeren har skrevet selv. - En mellomvare (middleware) leser tokenet og verifiserer signaturen.
- Er signaturen gyldig, legger mellomvaren opplysningene om brukeren i
req.user.
req.user finnes ikke i Express fra før, men lages av koden. Feltet kan bare stoles på fordi koden verifiserer tokenet riktig.
Riktig verdi fra feil kilde
Si at arkitekturen bestemmer at bare eieren av et dokument får endre det:
// Farlig: brukeren oppgir selv hvem han er
if (dokument.eierId !== req.body.brukerId) return res.sendStatus(403);
// Trygg: identiteten kommer fra det verifiserte tokenetif (dokument.eierId !== req.user.id) return res.sendStatus(403);
Syntaksen er feilfri i begge linjene, og semantikken er tydelig i begge. Forskjellen ligger i sammenhengen. Serveren kan ikke stole på avsenderen, og da er bare den ene kilden trygg. Regelen i arkitekturen er riktig, men den første linjen håndhever den ikke.
OWASP, en internasjonal ideell organisasjon for programvaresikkerhet, har egne navn på denne typen feil. Når en bruker får tilgang til et objekt han ikke eier, heter det Broken Object Level Authorization. Det er den største risikoen på organisasjonens liste over sårbarheter i API-er.
Injeksjon
Injeksjon er et angrep der data fra brukeren blir tolket som kode. Her er syntaksen selv kjernen i problemet. Angrepet oppstår når et program lager en setning i et annet språk ved å lime inn det brukeren har sendt.
Et vanlig eksempel er SQL, språket som brukes til å hente data fra databaser:
// Farlig: brukerens data blir en del av SQL-setningen
const sql = `SELECT * FROM dokumenter WHERE id = '${req.params.id}'`;const { rows } = await pool.query(sql);
Sender brukeren ' OR '1'='1 som ID, endres selve strukturen i SQL-setningen. Databasen leser ikke lenger «hent dokumentet med denne ID-en», men «hent alle dokumenter». Brukerens data har krysset grensen fra data til syntaks.
// Trygg: data og syntaks holdes adskilt
const { rows } = await pool.query(
'SELECT * FROM dokumenter WHERE id = $1',
[req.params.id]);
Dette kalles en parameterisert spørring. SQL-setningen og verdien sendes hver for seg, og databasen tolker $1 som en plass for data. Uansett hva brukeren sender, kan det aldri bli en del av syntaksen.
Mass assignment
Mass assignment er en sårbarhet der et program lagrer alt brukeren sender, uten å sjekke hvilke felter brukeren har lov til å endre. Eksemplet viser hvordan alle tre lagene henger sammen. Skrivemåten ser trygg ut, men betydningen er en annen, og sammenhengen gjør den farlig.
// Farlig: alt brukeren sender, lagres
const data = req.body as Profiloppdatering;await db.bruker.update({ where: { id: req.user.id }, data });
I TypeScript er as en typepåstand. Den ser ut som en sjekk, men den er det ikke. TypeScript oversettes til JavaScript før koden kjører, og da fjernes alle typer. Sender brukeren { "navn": "Ola", "rolle": "admin" }, blir rollen lagret.
1// Trygg: bare tillatte felter slippes gjennom, sjekket ved kjøretid23const Profiloppdatering = z.object({ navn: z.string().max(100), epost: z.string().email(), }).strict();
const data = Profiloppdatering.parse(req.body);await db.bruker.update({ where: { id: req.user.id }, data });
Her beskriver et skjema, laget med biblioteket Zod, nøyaktig hvilke felter som er lov. .strict() avviser alle felter som ikke står i skjemaet, og sjekken skjer mens koden kjører. OWASP plasserer denne typen feil under Broken Object Property Level Authorization, den tredje største risikoen på listen for API-er.
GitHub i 2012
I mars 2012 viste utvikleren Egor Homakov hvor alvorlig mass assignment kan være. GitHub var bygd med rammeverket Ruby on Rails, som den gang lagret alle felter som standard. Homakov hadde varslet om problemet, men ble i stor grad oversett.
Han utnyttet da svakheten i skjemaet for å oppdatere offentlige nøkler. Slik la han sin egen nøkkel til på Ruby on Rails-prosjektet og fikk administratortilgang. Han publiserte en ufarlig fil for å vise at det virket. GitHub stengte kontoen hans i noen timer, rettet feilen og åpnet kontoen igjen da det var klart at han ikke hadde ondsinnede hensikter (The Register).
Arkitektur og kode
Arkitekturen bestemmer reglene, og koden håndhever dem. Et system beskrives på flere nivåer, fra det overordnede til det konkrete:
- Arkitekturen beskriver hvilke deler systemet består av, hvordan dataene flyter, og hvor grensene for tillit går.
- Designet av hver del beskriver hvordan en regel skal oppfylles, for eksempel at eier-ID alltid hentes fra den verifiserte identiteten.
- Implementasjonen er selve koden, der regelen blir skrevet som syntaks.
Arkitekturen planlegges ikke ned til hver kodelinje. Mellom regelen og linjen ligger et gap som bare utvikleren fyller. Det er i det gapet req.body kan bli skrevet der det skulle stått req.user.
To kilder til svakhet
OWASP skiller mellom feil i designet og feil i implementasjonen, fordi de oppstår på ulike tidspunkt og må rettes på ulike måter. Et sikkert design kan fortsatt ha feil i koden som kan utnyttes. Et usikkert design kan ikke reddes av feilfri kode, fordi kontrollene som trengs aldri ble laget (OWASP Top 10:2025).
Arkitekturen kan likevel gjøre det vanskeligere å skrive feil. Et felles lag som validerer all data fra brukeren, databasebiblioteker som bare tillater parameteriserte spørringer, og verktøy som stopper kjente farlige mønstre, er eksempler på dette. Slike grep begrenser valgene, men fjerner ikke behovet for å forstå koden. Den som bruker komponentene, må fortsatt kunne lese hva de gjør.
Kunstig intelligens
Språkmodeller som skriver kode, produserer i dag stort sett gyldig syntaks. Koden kompilerer og ser ferdig ut. Når syntaksfeilene blir færre, er det feilene i semantikken og pragmatikken som blir igjen.
Antakelser
Den som ber en modell skrive kode, beskriver oppgaven ofte på arkitekturnivå, for eksempel «bare eieren får endre dokumentet». Alt beskrivelsen ikke sier, må modellen anta. Skal eier-ID-en hentes fra req.user eller fra req.body? Kode bygger alltid på antakelser, men her er det ingen som har valgt dem bevisst.
Å beskrive alt ned til hver linje løser ikke problemet. En beskrivelse som er detaljert nok til å være fullstendig, er i praksis selve programmet. I de fleste systemer skrives dessuten arkitekturen underveis, fordi kravene endrer seg. Det finnes sjelden en ferdig plan å gi modellen.
Kodebasen som mal
Det modellen ikke får beskrevet, henter den ofte fra koden som allerede finnes. Sjekker resten av kodebasen eierskap mot den verifiserte identiteten, vil ny kode ofte følge samme mønster. Er kodebasen slurvete, blir slurvet gjentatt.
Uansett hvem som skriver koden, er det lesingen som avslører antakelsene. Gapet mellom regelen og linjen lukkes av den som leser koden og forstår hva den gjør. Det forutsetter at man kan syntaksen.

