Koodarin näkökulmasta: kevyt kokeilu paljasti varomattoman AI-koodauksen vaarat ohjelmistokehityksessä

11 | 2025 Erkka Korpi, Senior software & cloud engineer, Partner Kipinä

Hyväksi todetut toimintatavat sivuuttamalla kevyt AI-koodauskokeilu paljasti liian rohkean koodauksen haavoittuvuudet

Päätin "vibe koodata" omien lasten rahan ansaintaa ja käyttöä seuraavan sovelluksen ja otin tietoisesti lähestymiskulmaksi, etten itse koske tai katso koodiin, vaan teen kaiken promptien kautta käyttäen Cursoria agentti moodissa. Lähdin liikkeelle hyvin suoraviivaisesti kuvailemalla mitä haluan, ja aina Cursorilta saadun toteutuksen jälkeen pyysin uusia featureita ja pienempiä korjauksia olemassa oleviin.

Noin kahden tunnin kohdalla aloituksesta minulla oli lokaalisti ja AWS:ssä pyörivä sovellus valmiina.

Tässä kohtaa oli aika tutkia tarkemmin Cursorin tuottamaa toteutusta. Hyvin nopeasti selvisi, että kokonaisen toteutuksen koodillinen määrä oli toista tuhatta riviä. Havainto oli selvä: sovelluksen omistajuus vaatisi koodin tarkkaa tutkimista, jotta minulla olisi täysi ymmärrys siitä, miten koodi tarkalleen toimii, millaisia komponentteja ja kirjastoja se sisältää sekä miten koodin logiikka etenee.

Jos miettii perinteistä tapaa kehittää sovelluksia, ero on siinä, että perinteisesti ollaan itse kuskin paikalla koko ajan. Joudut itse tekemään valinnat ja päätökset siitä, miten ohjelman tulee toimia ja miten haluttuun lopputulokseen päästään, ja täten tunnet ohjelman sisällön ja toiminnan. Todettakoon siis, että omassa kokeilussani en seurannut lainkaan Antti Rintalan blogissaan nostamia hyviä käytäntöjä AI-pohjaiseen softakehitykseen, jolla oli suora vaikutus Cursorin tuottaman koodin laatuun.

Tämän blogin tarkoitus on siis jakaa, mitä havaintoja tällainen kokeilu toi esiin ja millaisia riskejä tekoälypohjainen ohjelmistokehitys voi sisältää silloin, kun koodia ei oikeasti ymmärretä.

 

Mitä kevyt kokeilu paljasti

Ohjelmistokehittäjänä osasin vaatia AI:lta tiettyjä asioita, jotka eivät ole välttämättä itsestäänselviä sellaisille henkilöille, joilta puuttuu kokemus ohjelmistokehityksestä.

Sovellukseeni tuli kirjautuminen eri käyttäjille, ja ensimmäisessä toteutuksessa AI tallensi käyttäjien salasanat täysin paljaana tietokantaan. Kirjautumisessa sovellus teki haun, jossa kysyttiin, onko käyttäjän kirjautumissivulla antama salasana 1:1-match tietokannassa olevaan. Tämä avasi mahdolliselle hyökkääjälle suoran SQL-injektiohaavoittuvuuden.

Osasin ohjeistaa Cursoria käyttämään bcrypt-kirjastoa hashamaan salasanan ja tallentamaan plain text -salasanan sijaan hashatun merkkijonon tietokantaan. Lisäksi muutin kirjautumislogiikan niin, että kirjautuessa salasanasta lasketaan hash-arvo, jota verrataan tietokannassa olevaan. Näin sovellus ei tee suoraa hakua tietokantaan annetulla salasanalla ja mahdollinen injektiohaavoittuvuus poistuu.

 

Kuitenkin myöhemmin huomasin, että Cursor oli jättänyt käyttäjille hashattavat salasanat kovakoodattuna koodiin, jolla tietokanta alustetaan. Kokenut devaaja olisi automaattisesti ymmärtänyt, että vastaavassa tilanteessa salasanat tulisi tuoda dynaamisesti ympäristömuuttujien kautta.

 

Implementaatiovaiheessa npm install -prosessien aikana vilahti terminaalissa viestejä, joissa npm kehoitti päivittämään vanhentuneita kirjastoversioita uudempiin. Koska sovellukseni ei ollut millään tavalla kriittinen, päätin jättää nämä huomioimatta. Mutta on sanomattakin selvää, että oikeassa asiakasprojektissa olisi syytä tutustua tarkkaan kirjastoihin ja niiden versioihin, jotteivät ne sisällä haavoittuvuuksia, jotka voisivat vaarantaa sovelluksen.

Kun koodia alkoi tutkia tarkemmin, selvisi, että Cursor oli muun muassa kirjoittanut session cookien generointia varten tarvittavan salaisuuden kovakoodattuna koodiin, ja session-hallinta sisälsi CSRF-haavoittuvuuden, jonka avulla samassa selainsessiossa ajettavalla verkkosovelluksella pystyttäisiin lähettämään HTTP-kutsuja sallitusti sovellukseen.

Koodin tarkastelussa kävi siis selväksi, että Cursor ei siivonnut omia jälkiään, kun siirryttiin lokaalista kehitysversiosta julkisesti saatavilla olevaan versioon. Tämän myötä tuotannossa ajettava sovellus sisälsi jopa kriittisiä haavoittuvuuksia.

Lisäksi Cursor valitsi kieleksi JavaScriptin esimerkiksi TypeScriptin sijaan, mikä on lähtökohtaisesti alttiimpi ongelmille ja haavoittuvuuksille dynaamisen tyypityksen vuoksi.

 

AI-koodauksen ohjelmointikielen valinnassa ei ole absoluuttisia totuuksia

Yksi AI-vetoisen kehityksen keskeisistä haasteista on ohjelmointikielen valinta. Kielen valinta on aina mielipiteitä ja usein myös tunteita herättävä aihe.

Koodikieliä voisi tietyllä tapaa verrata autoihin: on tavallisia perusautoja kuten Toyota Corolla, ja sitten on niitä todella nopeita, kuten vaikkapa Ferrari. Moni arvostaa turvallisuutta ja toimintavarmuutta, kun taas toiset haluavat vauhtia. Mutta kovaa ajaminen on muutakin kuin kaasun painamista. Tarvitaan taitoa ja ymmärrystä auton toiminnasta ja käyttäytymisestä. Jos tavallinen kuljettaja laitetaan Ferrarin rattiin ja käsketään ajamaan kovaa, matka saattaa päättyä ennenaikaisesti juuri puuttuvien taitojen ja tietojen takia.

Tätä samaa analogiaa voi soveltaa ohjelmointikieliin. Toiset kielet ovat kuin perusvarma Corolla: niillä pääsee perille, mutta eivät ne vauhdilla hurmaa. Toiset taas ovat kuin urheiluauto, joka vaatii osaamista pysyäkseen tiellä.

Web-sovelluksia ohjelmoitaessa on perinteisesti valittu korkeamman tason kieliä, kuten Java, Python, Go, TypeScript tai JavaScript, ja syyt näihin valintoihin ovat kokeneille koodareille selviä. Mutta jos projektia tehdään AI-vetoisesti ilman kokenutta ihmistä puikoissa, voidaan tietämättä astua sudenkuoppiin, jotka olisivat kokeneelle kehittäjälle ilmeisiä.

Nykyaikana suosiota ovat saaneet modernit matalamman tason kielet Rust ja Zig, sekä ikivihreät klassikot C ja C++. Syyt näiden käyttöön liittyvät usein suorituskykyyn. Rust on saanut mainetta turvallisuudesta ja tehokkuudesta ja muun muassa tästä syystä monet yritykset, kuten InfluxDB, ovat uudelleenkirjoittaneet tuotteitaan Rustilla.

Tietokantatuotetta rakentavalla yrityksellä voi olla täysin erilaiset tarpeet ohjelmointikielen suorituskyvyn suhteen kuin vaikkapa sähköyhtiön asiakasportaalia rakentavalla tiimillä.

Korkeamman tason kielet, kuten Java, suojaavat meitä jopa tietämättämme asioilta, joita matalamman tason kielet antavat tehdä. Kun yhtälöön lisätään kokematon ohjelmoija, avautuu paljon suurempi riskimaailma kuin korkeamman tason kielissä. Yksi näistä riskeistä liittyy muistinhallintaan ja esimerkiksi buffer overflow -tyyppisiin haavoittuvuuksiin.

Vaikka Rust suojaa ohjelmoijaa monilta virheiltä, senkin kanssa voi "ampua itseään jalkaan", jos ei tiedä mitä tekee tai ei osaa vaatia AI:ta huomioimaan tällaisia riskejä. Google ja Microsoft ovat raportoineet, että jopa 70 % haavoittuvuuksista liittyy muistin hallintaan.

Ottaen huomioon, miten ilmeisiä haavoittuvuuksia Cursor jätti omaan JavaScript-projektiini ja kuinka helposti ne oli löydettävissä, on helppo uskoa, että matalamman tason kielillä vastaavat ongelmat voisivat jäädä syvemmälle piiloon, vaikka kieli itsessään olisi lähtökohtaisesti turvallinen.

Kun puhutaan rajapintaprojekteista, joissa API toimii esittävän kerroksen ja tietokannan välissä, pullonkaula syntyy usein muualle kuin ohjelmointikieleen, esimerkiksi I/O-operaatioihin, tietoliikenteeseen ja arkkitehtuurisiin ratkaisuihin.

Äärimmäisissä tapauksissa kokematon koodari voi AI-avusteisesti lähteä rakentamaan omaa tiedonsiirtoprotokollaa tai salausalgoritmeja, jotka kyllä voi toteuttaa AI:n avulla, mutta joihin liittyy merkittäviä riskejä.

Jos suorituskyvyllä on merkitystä, Java ja Go ovat hyvin tehokkaita korkeamman tason kieliä, jolla on laaja, sisäänrakennettu sekä kolmannen osapuolen, kirjastotuki yleisimpiin kehitystarpeisiin. Jos taas halutaan TypeScript/JavaScript-pohjainen toteutus, kannattaa tutustua projektiin nimeltä Bun. Bun on Zig-pohjainen runtime TypeScriptille ja JavaScriptille ja sisältää sisäänrakennettuna monia ominaisuuksia, kuten HTTP-palvelimen. Tämä vähentää kolmannen osapuolen kirjastoja ja pienentää hyökkäyspintaa.

 

Mitä tästä opimme – ja miten varmistat, ettei AI-koodaus johda ongelmiin

AI-avusteinen kehitys voi nopeuttaa tekemistä valtavasti, mutta vastuu koodin laadusta ja turvallisuudesta on edelleen ihmisellä.


Poimi tästä kymmenen kohdan muistilista, jonka avulla vältät yleisimmät sudenkuopat:

1) Poista kaikki kovakoodatut salaisuudet ja siirrä ne ympäristömuuttujiin.

2) Varmista, että tietokantakyselyt parametrisoidaan ja salasanat tallennetaan hashattuna (esim. bcrypt tai argon2).

3) Suorita SAST/DAST-skannit ja riippuvuusauditit (npm audit, Snyk).

4) Tarkista CSRF-suojaus ja session-hallinta.

5) Lukitse kirjastoversiot (package lock / pinned versions).

6) Lisää automaattitestit – unit-, integraatio- ja regressiotestit.

7) Dokumentoi arkkitehtuuripäätökset ja käytetyt paketit.

8) Pyydä koodikatselmusta kokeneelta kehittäjältä.

9) Arvioi ohjelmointikielen ja runtime-valinnan vaikutus turvallisuuteen ja ylläpidettävyyteen.

10) Luo rollback- ja monitorointisuunnitelma tuotantoon vientiä varten.

AI voi kirjoittaa koodin nopeasti, mutta vain ihminen voi varmistaa, että se on turvallinen, ylläpidettävä ja oikeasti ymmärretty.

Jos haluat varmistaa, että AI-avusteinen kehitystyösi on turvallista ja kestävällä pohjalla, jutellaan. Kipinän tiimi auttaa arvioimaan AI-koodin laatua, arkkitehtuuria ja tietoturvaa – inhimillisesti, ymmärrettävästi ja faktoihin nojaten.

Ota yhteyttä ja varmistetaan, että seuraava AI-projektisi rakentuu oikein alusta alkaen.

Ota yhteyttä
Kiinnostaako koodarin näkökulma?

 
 

Erkka Korpi, Senior software & cloud engineer & Partner Kipinä

Kirjoittajana on yksi Kipinän kokeneista asiantuntijoista. Erkan suoraselkäinen ja utelias luonne luo intohimon ymmärtää miten asiat todella toimii syvällä pinnan alla. Tietotekniikka on Erkan intohimo niin konsulttina Supercellin tiimissä, kuin vapaa-ajalla: “tykkään mennä sinne todella syvään päätyyn.”

Seuraava
Seuraava

Nordic Business Forum 2025 Kipinän silmin: 4 oppia tulevaisuuteen