A coder's perspective: a lightweight experiment revealed the dangers of careless AI coding in software development

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

Ignoring best practices, a lightweight AI coding experiment exposed the vulnerabilities of overly bold coding

I decided to "vibe code" an app following the earning and spending of my own children's money and I deliberately took the approach of not touching or looking at the code myself, but doing everything through prompts using Cursor in agent mode. I started off very straightforwardly by describing what I wanted, and each time I got an implementation from Cursor I asked for new features and minor tweaks to existing ones.

About two hours after starting, I had an application running locally and on AWS.

At this point, it was time to take a closer look at the implementation provided by Cursor. Very quickly, it became clear that the entire implementation was in the order of a few thousand lines of code. The realisation was clear: ownership of the application would require a close examination of the code in order for me to have a full understanding of exactly how the code works, what components and libraries it contains, and how the code logic flows.

If you think about the traditional way of developing applications, the difference is that traditionally you are in the driver's seat all the time. You have to make the choices and decisions about how the application should work and how to get to the desired end result, and thus you know the content and how the application works. In my own experiment, therefore, I did not follow at all good practices for AI-based software development highlighted by Antti Rintala in his blogwhich had a direct impact on the quality of the code produced by Cursor.

So the purpose of this blog is to share the findings of this experiment and the risks of AI-based software development when the code is not really understood.

 

What the light experiment revealed

As a software developer, I was able to demand certain things from AI that are not necessarily obvious to people who lack experience in software development.

My application had logins for different users, and in the first implementation, the AI stored the users' passwords completely bare in the database. At login, the application performed a query asking if the password given by the user on the login page was a 1:1 match to the one in the database. This opened up a potential attacker to a direct SQL injection vulnerability.

I was able to instruct Cursor to use the bcrypt library to hash the password and store the hashed string in the database instead of the plain text password. I also changed the login logic so that when logging in, the password is hashed and compared to the hash value in the database. This way, the application does not perform a direct lookup in the database with the given password and the potential injection vulnerability is eliminated.

 

However, I later discovered that Cursor had left hashable passwords for users hard-coded into the code used to initialize the database. An experienced dev would have automatically understood that in a similar situation, passwords should be imported dynamically via environment variables.

 

During the implementation phase, during the npm install processes, messages flashed in the terminal asking npm to update outdated library versions to newer ones. Since my application was not critical in any way, I decided to ignore these. But it goes without saying that in a real client project, one should carefully study the libraries and their versions to make sure they don't contain vulnerabilities that could compromise the application.

When the code was examined further, it was discovered that Cursor had, among other things, hard-coded the secret for generating session cookies into the code, and the session manager contained a CSRF vulnerability that would allow a web application running in the same browser session to send HTTP calls to the application without permission.

So it was clear from looking at the code that Cursor did not clean up its own mess when moving from a local development version to a publicly available version. As a result, the application running in production even contained critical vulnerabilities.

In addition, Cursor chose JavaScript as the language instead of, for example, TypeScript, which is inherently more prone to problems and vulnerabilities due to its dynamic typing.

 

There are no absolute truths when choosing a programming language for AI coding

One of the key challenges in AI-driven development is the choice of programming language. The choice of language is always a matter of opinion and often emotion.

In a way, code languages could be compared to cars: there are the basic ordinary cars like the Toyota Corolla, and then there are the really fast ones, like the Ferrari. Many people value safety and reliability, while others want speed. But there's more to driving fast than just pushing the accelerator. It takes skill and an understanding of how the car works and behaves. If you put an ordinary driver behind the wheel of a Ferrari and tell him to drive fast, the journey may end prematurely precisely because of a lack of skills and knowledge.

The same analogy can be applied to programming languages. Some languages are like a basic Corolla: they get the job done, but they don't have the speed. Others are like a sports car that needs to know how to stay on the road.

Traditionally, when programming web applications, higher-level languages such as Java, Python, Go, TypeScript or JavaScript have been chosen, and the reasons for these choices are clear to experienced coders. But if a project is AI-driven without an experienced human at the helm, you can unwittingly step into pitfalls that would be obvious to an experienced developer.

In modern times, the modern lower-level languages Rust and Zig have gained popularity, as have the evergreen classics C and C++. The reasons for using these are often performance related. Rust has gained a reputation for security and efficiency, which is one of the reasons why many companies, such as InfluxDB, have rewritten their products in Rust.

A company building a database product may have completely different needs in terms of programming language performance than, say, a team building a customer portal for an electricity company.

Higher-level languages like Java protect us, even without our knowledge, from things that lower-level languages allow us to do. Add an inexperienced programmer into the equation and a much bigger world of risk opens up than in higher-level languages. One of these risks has to do with memory management and, for example buffer overflow -type vulnerabilities.

Although Rust protects the programmer from many mistakes, even with it you can "shoot yourself in the foot" if you don't know what you're doing or don't know how to ask the AI to take such risks into account. Google and Microsoft have reported that even 70% of vulnerabilities are related to memory management.

Given how obvious the vulnerabilities Cursor left in my own JavaScript project and how easy they were to find, it is easy to believe that similar problems in lower-level languages could be hidden deeper, even if the language itself is inherently safe.

When it comes to interface projects, where the API acts as an interface between the presentation layer and the database, the bottleneck often arises in areas other than the programming language, such as I/O operations, data traffic and architectural solutions.

In extreme cases, an inexperienced coder may, with the help of AI, set out to build his own data transfer protocol or encryption algorithms, which can be implemented with AI but involve significant risks.

If performance matters, Java and Go are very powerful higher-level languages with extensive, built-in and third-party, library support for most common development needs. If you want a TypeScript/JavaScript-based implementation, you should look at a project called Bun. Bun is a Zig-based runtime for TypeScript and JavaScript and includes many built-in features, such as an HTTP server. This reduces the number of third-party libraries and reduces the attack surface.

 

What we learn from this - and how to make sure AI coding doesn't lead to problems

AI-assisted development can speed up things enormously, but the responsibility for the quality and security of the code remains with humans.


Here's a ten-point checklist to help you avoid the most common pitfalls:

1) Delete all hard-coded secrets and move them to the environment variables.

2) Make sure that database queries are parameterised and passwords are stored hashed (e.g. bcrypt or argon2).

3) Run SAST/DAST-scans and dependency audits (npm audit, Snyk).

4) Check CSRF protection and session management.

5) Lock the library versions (package lock / pinned versions).

6) Add automated tests - unit, integration and regression tests.

7) Document the architectural decisions and packages used.

8) Ask for a code review from an experienced developer.

9) Evaluate the impact of programming language and runtime choice on security and maintainability.

10) Create a rollback and monitoring plan for export to production.

AI can write code quickly, but only a human can ensure that it is secure, maintainable and properly understood.

If you want to make sure your AI-assisted development work is safe and sustainable, let's talk. The Spark team can help you assess the quality, architecture and security of your AI code- in a human, understandable and fact-based way.

Contact us to make sure your next AI project is built right from the start.

Contact us
Interested in the coder's perspective?

 
 

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

The author is one of Kipinä's experienced experts. Erka's straightforward and inquisitive nature creates a passion for understanding how things really work deep beneath the surface. IT is Erka's passion both as a consultant in the Supercell team and in his spare time: "I like to go in there really deep."

Previous
Previous

Humans and Machines: Building Learning Cultures in the Age of AI

Next
Next

Nordic Business Forum 2025: 4 lessons for the future