From a developer's perspective: A lightweight experiment reveals the hidden dangers of careless AI coding in software development

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

By discarding proven best practices, a lightweight AI coding experiment exposed the vulnerabilities of overconfident automation

I decided to "vibe code" an application to help my children earn and manage money. I deliberately chose an approach where I would not look at or touch the code myself, driving the entire process exclusively through prompts using Cursor in agent mode. I started very simply by describing what I wanted, and after each implementation delivered by Cursor, I requested new features and minor fixes to the existing codebase.

Roughly two hours after starting, I had a fully functional application running locally and on AWS.

At this point, it was time to take a closer look at the implementation generated by Cursor. It quickly became apparent that the total codebase spanned over a thousand lines. The conclusion was clear: owning the application would require a thorough code review to ensure a complete understanding of how the code operates, what components and libraries it incorporates, and how the underlying logic executes.

Compared to traditional software development, the fundamental difference is that you are normally in the driver's seat at all times. You must make the choices and decisions regarding how the program should function and how the desired outcome is achieved, giving you an intimate knowledge of the program's content and behavior. It is worth noting that during my experiment, I completely ignored Antti Rintala's blog post highlighting best practices for AI-driven software development, which directly impacted the quality of the code Cursor generated.

The purpose of this blog post is therefore to share the insights gained from this experiment and highlight the risks involved in AI-driven software development when the underlying code is not genuinely understood.

 

What the lightweight experiment revealed

As a software developer, I knew to demand certain things from the AI that might not be obvious to individuals without a software development background.

My application included user authentication, and in the initial implementation, the AI stored user passwords in plain text in the database. During login, the application performed a query checking for a direct 1:1 match between the password entered on the login page and the database record. This exposed the system to a direct SQL injection vulnerability.

I instructed Cursor to use the bcrypt library to hash passwords, ensuring that hashed strings rather than plain text passwords were saved to the database. Additionally, I refactored the login logic so that the authentication process hashes the entered password and compares it against the stored hash. This eliminates direct database queries using raw passwords and removes the potential injection vulnerability.

 

However, I later noticed that Cursor had left user passwords hashed and hardcoded into the database initialization script. An experienced developer would have automatically understood that in such a scenario, passwords should be injected dynamically via environment variables.

 

During the implementation phase, while running `npm install`, the terminal flashed messages prompting me to update outdated library versions to newer ones. Since my application was in no way critical, I decided to ignore these warnings. Needless to say, in a real client project, one should thoroughly examine libraries and their versions to prevent vulnerabilities that could compromise the application.

Upon closer inspection of the code, it turned out that Cursor had, among other things, hardcoded the secret required for session cookie generation, and the session management contained a CSRF vulnerability that would allow a web application running in the same browser session to successfully send unauthorized HTTP requests to the application.

Reviewing the code thus revealed that Cursor did not clean up after itself when transitioning from a local development version to a publicly available one. Consequently, the application running in production contained critical vulnerabilities.

Furthermore, Cursor chose JavaScript instead of, for example, TypeScript, which is inherently more prone to issues 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. Selecting a language is always a topic that stirs opinions and often emotions.

Programming languages could in some ways be compared to cars: you have standard, basic cars like the Toyota Corolla, and then you have the really fast ones, like a Ferrari. Many value safety and reliability, while others crave speed. However, driving fast involves much more than just pressing the gas pedal. It requires skill and an understanding of how the car functions and behaves. If an average driver is put behind the wheel of a Ferrari and told to drive fast, the journey might end prematurely precisely due to a lack of skills and knowledge.

This same analogy applies to programming languages. Some languages are like a dependable Corolla: they get you to your destination, but they don't thrill you with speed. Others are like a sports car, requiring expertise to stay on the road.

When developing web applications, higher-level languages such as Java, Python, Go, TypeScript, or JavaScript have traditionally been chosen, and the reasons for these choices are obvious to experienced coders. However, if a project is built via AI-driven processes without an experienced human at the helm, one can unknowingly step into pitfalls that would be glaringly obvious to a seasoned developer.

Nowadays, modern lower-level languages like Rust and Zig, alongside evergreen classics C and C++, have gained popularity. The reasons for using these often relate to performance. Rust has built a reputation for safety and efficiency, which is why many companies, such as InfluxDB, have rewritten their products in Rust.

A company building a database product may have entirely different performance requirements for a programming language compared to, say, a team building a customer portal for an energy company.

Higher-level languages, such as Java, protect us from things we aren't even aware of—things that lower-level languages allow us to do. When an inexperienced programmer is added to the equation, a much larger risk landscape opens up compared to higher-level languages. One of these risks relates to memory management and vulnerabilities such as buffer overflows.

Although Rust protects the programmer from many mistakes, it is still possible to "shoot yourself in the foot" if you don't know what you are doing or fail to instruct the AI to account for such risks. Google and Microsoft have reported that up to 70% of vulnerabilities are related to memory management.

Considering how obvious the vulnerabilities were that Cursor left in my JavaScript project and how easy they were to uncover, it is easy to believe that with lower-level languages, similar issues could remain deeply hidden, even if the language itself is inherently secure.

When discussing API projects where the API acts as a layer between the presentation tier and the database, the bottleneck often arises elsewhere than the programming language—for example, in I/O operations, network communication, and architectural decisions.

In extreme cases, an inexperienced coder using AI assistance might start building custom data transfer protocols or encryption algorithms, which can indeed be implemented with AI's help, but which carry significant risks.

If performance matters, Java and Go are highly efficient higher-level languages with extensive built-in and third-party library support for common development needs. If, on the other hand, a TypeScript/JavaScript-based implementation is desired, it is worth looking into 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 reliance on third-party libraries and minimizes the attack surface.

 

What we learn from this—and how to ensure AI coding doesn't lead to trouble

AI-assisted development can accelerate delivery immensely, but the responsibility for code quality and security ultimately rests with the human.


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

1) Remove all hardcoded secrets and move them to environment variables.

2) Ensure that database queries are parameterized and passwords are stored securely using hashing (e.g., bcrypt or argon2).

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

4) Verify CSRF protection and session management.

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

6) Implement automated tests – unit, integration, and regression tests.

7) Document architectural decisions and dependencies used.

8) Request a code review from an experienced developer.

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

10) Establish a rollback and monitoring plan for production deployment.

AI can write code rapidly, but only humans can ensure it is secure, maintainable, and thoroughly understood.

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

Contact us and let's ensure your next AI project is engineered correctly from the ground up.


 
 

Erkka Korpi, Senior Software & Cloud Engineer & Partner at Kipinä

This article was written by one of Kipinä's seasoned experts. Erkka's principled and inquisitive nature drives his passion for understanding how things truly work deep beneath the surface. Technology is Erkka's passion both as a consultant in Supercell's team and in his free time: “I like to dive into the deep end.”

Previous
Previous

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

Next
Next

Nordic Business Forum 2025: 4 lessons for the future