Who Owns Code Security? Johannes Dahse on Developer Responsibility, Tooling, and AI

Open on YouTube ↗
Overview

What should software engineers know about writing secure code, and who in an organization should be responsible for it? In this episode of The Pragmatic Engineer, the host talks with Johannes Dahse, VP of Code Security at Sonar, who has worked in security for about 20 years. Dahse's central argument is that code security belongs to developers, not security teams. Vulnerabilities live in code, and only developers write and change that code. The conversation covers what that ownership involves in practice, which tools support it, how dependencies and developer machines bring in risk, and how AI coding assistants are changing both the threats and the defenses.

30 min read

From Getting Hacked to Professional Penetration Testing

Dahse traces his interest in security back roughly 20 years, to when his own computer was infected by a widespread piece of malware. The experience was frustrating, but it also left him curious about how someone had gotten into his machine. During his school years he started experimenting with things like Trojan horses. Later he moved to a German university where IT security could be studied as a subject.

There he found capture-the-flag (CTF) competitions. University teams connect to an isolated online environment and try to hack each other for points. Dahse says he became "obsessed" with these competitions and calls them his best learning experience. Earning money from the same skills as a student followed naturally. He worked for a couple of years as a freelance penetration tester for large companies, then wrote tools to help with vulnerability hunting. That work turned into a startup, which Sonar later acquired.

He describes penetration testing as a simulated attack. A company hires a hacker to find vulnerabilities within an agreed time frame and scope. In his engagements the goal was usually to get into the client's network by exploiting weaknesses in the applications they ran. The job then was to document how an attacker could get in, not to go further or damage anything, so the client could fix it.

How a Penetration Test Actually Works

Dahse separates black-box testing, where the tester has no access or inside knowledge and behaves like a real attacker, from white-box testing. In a typical black-box engagement against a web application, the tester uses the application from the outside and tries to picture the code behind it. What is this code probably doing, based on what I can see? What might a developer have forgotten? Experience teaches testers the typical mistakes. The aim is always to reach something sensitive to the business, such as stealing files or getting into the database.

The host asks about tools such as port scanners. Dahse says automation mostly handles the mapping phase: finding which endpoints and ports exist. Once there is a good picture of the landscape, he used to work manually, poking at the application to "see what breaks when you touch it." He calls that the most exciting part of the work.

Developers Should Own Code Security

The host asks who inside a company should own code security. Dahse says the industry currently treats it as shared, and the dominant view is that the security team owns security because it is in the team's name. He sees it the other way around. Every software vulnerability shows up in code, developers are the only people in an organization who write and change that code, and they are the only ones who can fix security issues. So developers should own their share of the code and the security problems in it. He adds that this is more realistic today than it used to be, because developers now have good education and good tools available to them.

He does not think security teams are useless. Application security is a much broader field than code security. It includes compliance requirements, organization-wide security initiatives, vulnerability reports from penetration tests, and new emerging threats. The larger an organization gets, the more it needs a security team for this work. What Dahse objects to is security teams spending their time on every issue that comes up during development. He calls it a waste for a security expert to look at yet another new cross-site scripting issue, build an exploit and a risk assessment for it, when a developer could simply fix it while coding and move on. Taking that work off security teams frees them for problems where their expertise really matters, such as cryptography or authentication logic. In those areas they can also help developers.

The host compares this to the split between product teams and platform teams. A platform team has specialized expertise and builds things other engineers use. Engineers can come to them with questions like how to store two petabytes of data. Dahse agrees with the comparison, with one caveat: most of the ownership should stay with developers. Security should be part of the development process, not something bolted on or run ad hoc whenever the security team decides.

Why the Shift Happened, and Why Security Tools Had to Change

The host points out that developers historically were not expected to own security. Dahse agrees that security teams clearly owned it 20 years ago. The work was driven by compliance, and development cycles were much slower. A team might ship quarterly, with a security team doing a final audit just before release. Today teams release several times a day or even per hour, and AI coding assistants speed things up further. A disconnected review after the fact no longer fits that pace.

He says the tools have to change too. Security products were historically built for security teams, and they carry a matching philosophy. An auditor wants to know about every potential issue and to "turn every stone." Put that philosophy into a fast development workflow and it becomes too noisy, because developers get interrupted constantly. His analogy is a security expert sitting in your passenger seat and yelling about every possible danger while you drive. That might be interesting for the first 50 meters, then becomes painful and annoying. His conclusion is that developers should own code security issues, and the tools for code security should also be built for developers. Broader application security tooling can stay with security teams.

What Counts as Code Security

Dahse admits he lacks a better definition, but offers this: secure code is code free of anything an attacker can use to exploit the application, get to data, and put the business at risk. The hard part is deciding what counts as a "security issue." People usually think of classic vulnerabilities such as SQL injection. He argues the category is much wider. A null pointer exception that crashes an application leaves it in an unintended state, and attackers can abuse that in some scenarios. A more obvious example is memory corruption in C/C++, where a buffer overflow can let an attacker execute code on the server. There are also logic problems. If users can upload a profile picture, a developer has to make sure an attacker cannot upload a shell to the server instead.

His conclusion is that security issues are, in the end, just bugs: things a developer forgot, or things that were specified wrongly. That makes them a form of technical debt, not very different from other bugs in the backlog that a developer needs to fix. He says this framing also makes it clearer why security is a developer problem.

The Basics Every Developer Should Know

The host acknowledges that the topic is "mushy," ranging from obvious null pointer exceptions to less obvious buffer overflows, and asks for the basics. Dahse first narrows the scope. Developers need to know how to prevent and patch issues. They do not need to master full exploitation techniques or be able to run an entire buffer overflow attack chain.

His first basic is to really know and understand what your code does. He admits this sounds "a bit silly and obvious," but says it is exactly how security experts find issues: they hunt for corner cases and edge cases the developer overlooked. In an era of AI-accelerated development and heavy use of libraries and open source, he says, it is no longer a given that developers know what their code does and how it interacts with the rest of the codebase. His practical advice is to look at security-sensitive features through an attacker's eyes and ask what an attacker could modify.

Input handling is the classic example: never trust any input. Dahse notes that input can be subtle. When someone uploads a video to YouTube and sets its title, that title is attacker-controllable input. Developers need to track where GET and POST parameters, cookies, and other external input end up. Input that reaches a file operation might let an attacker open arbitrary files. In a SQL query it becomes SQL injection. In an HTML response it becomes cross-site scripting. These issues have been around for a long time, they are still the most critical ones, and they still show up.

His second basic is secret leaks, which he says play a role in many well-known data breaches. A developer hardcodes an API token, a cryptographic key, or a database password, sometimes only temporarily for testing. Attackers now crawl public GitHub repositories, collect secrets, and test whether they still work. Deleting the code does not help, because the secret stays in the git history. He says this still happens "because we are humans."

Asked for a checklist, Dahse says the list changes over time as the industry learns and development practices shift. Some issue types become less common and new ones become more common. The basics he described, though, have been around for a long time and "apparently they don't go away." The host later suggests keeping an eye on the OWASP Top 10 to cover the fundamentals, and Dahse agrees.

Advanced Issues and the Dependency Problem

More advanced issues usually need specialist expertise. Examples include encryption that an attacker can still break, authentication logic, access privileges, and password reset flows, which he says often go wrong. His advice for these complex security features is not to reinvent the wheel. Use solid, community-vetted frameworks and libraries, and ask the security team for help choosing them.

The host brings up package poisoning in the Node ecosystem. An attacker takes over a package, injects malicious code, and everyone who depends on it, directly or downstream, is affected. Dahse calls this "a tough one." Everyone uses dependencies, those dependencies use their own dependencies, and when a maintainer is compromised and a package is backdoored, a developer who pulls it in has almost no chance of avoiding the problem. Not using dependencies isn't an option. What teams can do is put tools in place, specifically software composition analysis (SCA). These tools are updated very frequently with known threats. Once a package becomes known as vulnerable, malicious, or backdoored, SCA warns you and tells you which version to move to, or that you should drop the dependency altogether.

He explains how SCA works. It reads manifest files, meaning the list of dependencies for whatever package manager a project uses, and checks them against a database of known problems. He distinguishes these known vulnerabilities (CVEs), found and reported in someone else's code, from the zero-day vulnerabilities a developer writes into their own code. As an example, SCA can flag that a particular Log4j version in your project is vulnerable to the known Log4Shell vulnerability.

The CVE Program and Whether Developers Should Track It

Dahse describes the CVE list as a database run by MITRE with US government involvement, and notes that "some change is happening here." It used to be the central database for documenting known vulnerabilities. He believes so many vulnerabilities are now reported daily that it has become a bottleneck, and other databases and collection points are appearing. SCA tools typically draw on the CVE database plus other sources to cover as many known threats as possible.

The host asks whether a developer at a scale-up with one security engineer should try to keep up with CVEs personally, or push for more dedicated security staff. Dahse's answer is neither: use a tool. This problem can be automated, and he would not hire more security staff for it. By his figures, the database holds over 200,000 CVEs and about 50 new ones appear each day, though not all of them are in open-source libraries. Some affect commercial products. Reading every CVE is a poor use of time for developers and security team members alike. The more important thing is that the tool helps with fixing, not just detection. Building a huge backlog of security issues matters less than being able to fix them and getting advice on how.

Findings from Sonar's State of Code Security Report

Dahse says Sonar's analyzers scan about 750 billion lines of code daily. For the report, the team studied a subset: 8 billion lines of code written by 1 million developers across 40,000 organizations worldwide. The headline finding was roughly one security issue per 1,000 lines of code. He says this matches his own sense from manual code audits, and he considers it "quite a lot."

The most common issue types were the basics already discussed. The top five included log injection, cross-site scripting, SQL injection, and hardcoded passwords. One surprise was how often regular expressions appeared: slow or insecure regexes that can enable denial-of-service attacks.

The host notes the irony of using lines of code as a metric, since its value is so often debated. Dahse says it is a statistic built for the report, but the underlying point is that code quality is tightly linked to security. The same problem can be solved in more or fewer lines. More lines mean more code to review and more places where security issues can hide, while well-maintained and well-structured code makes issues easier to spot.

Code Quality Is a Security Issue

Dahse calls the link between quality and security "totally underrated" in the industry. Some cases are obvious bugs, such as the null pointer exceptions and slow regexes he mentioned. The less obvious case is unreadable, poorly maintained spaghetti code. Code that is hard to understand is hard to review, so during pair programming or code review, peers are more likely to miss security problems in it. Fixing is affected too. When an issue is found and reported back, a developer working in unmaintainable code may struggle to fix it, and the attacker's window stays open longer. He sees this as especially relevant now, because AI-generated code, in his observation, is typically of poor quality.

He places code security within the wider field of cybersecurity: data security, cloud security, network security, forensics. Large organizations need all of them as interconnected lines of defense. From an offensive perspective, he has always found application security the most interesting, because every organization ships software or runs online services, and those are available to attackers 24/7. Applications sit at the front line and are typically the first entry point into a network. Other disciplines focus more on stopping what happens next, for example making stolen data impossible to decrypt, or preventing lateral movement. He explains lateral movement as follows: after an attacker gets an initial foothold, such as a shell on one server with the ability to run system commands, they probe the internal network for other services they can reach. A broader security strategy is needed to stop that spread.

Developer Machines, Supply Chains, and AI Agents

The host suggests that MCP servers could become a tempting attack vector. An attacker might publish an MCP server that claims to do one thing but secretly does another, runs locally, and gives the attacker access to a developer's machine. He asks whether developer machines have been off limits so far.

Dahse says they have never been off limits. Supply chain attacks are a major topic precisely because developers build software that gets deployed across organizations worldwide. Compromising a developer's machine is how a popular npm package gets compromised. A software vendor needs to make sure the software it ships to thousands of organizations is not backdoored because one of its developers was compromised.

He agrees that agents, and the greater control developers hand over to them, create a new threat. The concern is no longer just dependencies or general machine security. It is also whether the agent is doing the right thing, and whether it has privileges that would let it do harm, accidentally or deliberately. His example: an agent reads a Jira ticket, and someone has written a malicious ticket instructing the agent to add a backdoor instead of solving the development problem. That, he says, is a new type of security problem teams have to think about.

The Code Security Toolbox

Dahse walks through the tools he sees teams use for basic security hygiene. First is linting in the IDE, which catches issues as you type. IDE checks and extensions usually stay syntactic and semantic, and usually only cover the current file, because they must run in milliseconds without slowing the developer down. Security coverage there is therefore limited in breadth and depth.

Static application security testing (SAST) goes deeper. Depending on the tool, it may use techniques such as symbolic execution or taint analysis. The whole codebase is turned into an abstract model and the analyzer simulates what could happen at runtime without executing anything. He describes the model as a large graph in which every file, function, function call, and if/else, meaning every point where control flow changes, becomes part of the structure. The analysis looks for places where user input enters the application, follows it through variable assignments, branches, and function calls, and checks whether it reaches something security-sensitive. These data-flow paths can be very long and complex. He says the analysis used to take days and now takes minutes, and that making it efficient is a very hard problem. The value is that it automates the "be mindful of user input" advice from earlier and can find long, tricky connections between input and sensitive operations.

Other categories include secret detection for hardcoded credentials, and infrastructure-as-code scanning, which covers things like GitHub Actions files. He notes that a good SAST tool typically handles these too, since all of it can be treated as code. Then there is SCA for known vulnerabilities in dependencies.

The host asks about the downside of using every tool on every codebase, even for a one-person startup. Dahse recommends static analysis and SCA at every level as basic hygiene. The caution is to pick tools designed for developers rather than security teams. A noise level that is fine for an auditor is, in his words, "deadly" for development productivity.

Dynamic Testing: DAST and Fuzzing

Dahse also covers dynamic tools. Dynamic application security testing (DAST) tries to automate a penetration test. It treats a running application, on a test server or in production, as a black box and sends all kinds of malicious payloads at it. It then watches how the application responds: whether it breaks, slows down, behaves oddly, or throws error messages. Fuzzing is similar but aimed more at embedded software and C/C++ binaries and libraries that process complex file formats or protocols. It flips every bit of the input to see what crashes, and he says this works very well.

He considers dynamic tools less suited to developers today. They are disconnected from the act of coding: you have to finish your work, deploy it to a test server, and run the tool, so the feedback loop is longer and requires a context switch. For security teams, though, he calls DAST and fuzzing great additional tools. The host adds that the setup effort alone makes them a better fit for security teams.

The Limits of AI Security Reviews

The host asks about the many AI security review tools now on the market. Dahse finds them "super fascinating" and says what AI can find today is impressive. He then raises three limitations for systematic use by developers.

The first is precision and scale. Finding issues is only part of the job. It also matters how often a tool reports things that are not true or not meaningful issues, and whether it can handle something like half a million lines of code. What he mostly sees today are security research agents that find issues somewhat at random. That is useful for security teams, but developers need something that systematically finds all code security problems.

The second is determinism. AI is non-deterministic. That matters little to a security team, but a development organization needs a quality gate that produces the same output consistently across all teams. A build should not fail because an issue randomly appears or disappears.

The third is what he calls a contradiction. If much of today's code is written by AI, then using AI to review it is like having students grade their own homework. If the AI could not avoid generating a security issue, he asks, why would it detect that issue afterward? He argues for guardrails and verification that are not themselves AI. The host notes some people would answer that a different LLM could do the review, but agrees that this does not change the core problem.

AI-Generated Code: More Code, More Risk

Dahse describes Sonar studies of popular LLMs, including Claude, GPT models, Llama, and OpenCoder. The studies characterized each model's "personality": what kinds of issues it produces and what quality of code it writes. One finding he highlights is that GPT-5's reasoning mode decreased the number of security issues, though it did not eliminate them, but it produced more verbose output and more code to solve the same problem. He sees that extra low-quality code as its own security risk. Even if a snippet contains fewer issues, it may cause problems when combined with other code, it is harder for peers to review, and it is less maintainable.

The host links this to the old saying that code is a liability. Experienced engineers would sometimes spend a day or two cutting lines of code, refactoring, removing duplication, and clarifying responsibilities. Taking the one-issue-per-thousand-lines figure at face value, the host argues, that effort is still worth it. Dahse agrees that developers should not just "vibe code" and accept everything, but make sure the code makes sense, is well structured, and keeps the architecture maintainable. This already matters for quality, and it adds to the security problem. He cites a Stack Overflow survey in which, as he recalls, only 3% of developers said they trust their AI-generated code, and calls that very reasonable. The host shares their own habit when building APIs with AI: when a file such as an index.ts becomes bloated, they stop the AI and have it refactor, because they need to understand and navigate their code. They speculate that people who vibe code may eventually learn the same lessons the hard way.

How AI Is Changing Security

Asked about AI's overall impact, Dahse starts with security tools themselves. Deterministic algorithms like static analysis can be enhanced with AI. Taint analysis, for example, needs detailed knowledge of millions of libraries and frameworks, and AI can help gather that knowledge and feed it into the deterministic engine. He expects such combinations in static analysis, dynamic analysis, and other areas. AI also works well for fixing. Dropping half a million lines of code into a context window does not work well, but a narrow task does. If you give the model a deterministically found issue, the 20 relevant lines of code, and a description of the problem, AI is very good at fixing it. He stresses this matters because the goal is fixing, not only detecting.

He also sees applications themselves changing. In a traditional stack, removing the database removes SQL injection, but adding an LLM to the backend adds prompt injection, where attackers manipulate the system prompt or prompt logic and interfere with the output. As text becomes code, he says, "prompt injection is kind of like the new code injection," because human language is now the new code. Attackers adapt to the changing threat landscape, and the industry adapts too, though that can take time. He points to how much COBOL code is still around.

For coding assistants, he names verification as the big new challenge. Writing code is no longer the bottleneck. Verifying that all the faster-produced code is secure, at scale and at speed, is. Unverified code leads to security issues directly, or to quality issues that turn into security problems later. The host mentions the common answer of scaling human code review with better tooling and context. Dahse agrees that automated verification during code production is key. He also mentions work at Sonar on the training side: making sure the data LLMs are trained on is free of common security and quality issues, so that models produce more secure code from the start. He expects to see more of this in the future.

On whether AI introduces new kinds of flaws, Dahse says the models make the same mistakes as humans, since they learn from human code, but the prevalence of different issue types shifts. Slopsquatting is his example. An AI suggests a library that does not exist, an attacker registers that name on npm or Maven Central, and the malicious package ends up in the project. The underlying issue resembles the older dependency confusion problem, but a human rarely mistypes a dependency name, while AI makes the mistake much more often. Going the other way, he speculates, clearly labeled as something he is not yet seeing, that simple one-line issues like hardcoded passwords might decline as AI learns to avoid them. Meanwhile more complex issues, which only emerge when multiple code snippets combine, might grow, since AI finds those harder to grasp.

Misconceptions About the Security Industry

Having moved from the security side toward the developer side over the years, Dahse finds it striking that security and development are such separate communities. Both talk about code and bugs, but one is oriented toward building and the other toward attacking and breaking. He sees three fallacies coming out of that split.

The first is treating security as a product. The industry is fascinated by security problems, driven by compliance and fear of breaches, and full of money. It sells products that promise security. He says a CISO can have a hard time knowing what to buy. The mistake is choosing a tool that finds another 1,000 issues when you press the scan button, instead of something built into the development process that engages developers and helps them fix things. He calls this the biggest fallacy. The second is the mystique that security can only be owned by top-notch hackers. He argues the lines are blurrier than that, and the work is mostly about fixing, not the exploitation stage the industry talks about and finds fascinating. He admits he is guilty of that fascination himself. The third is that perfect security does not exist.

The host compares vendor promises in security to developer productivity tools that promise results from measuring one metric. In both fields, a team with no tools but experienced engineers can outperform a team with every scanner. The host wonders whether some areas are simply hard because they have many moving parts. Dahse agrees that the more complex the software, the more hard problems, bugs, and security issues it will have. He describes Sonar's vulnerability research team picking some of the world's most popular open-source projects, which have strong communities, good maintainers, and paid bug bounty programs, and still finding something every time. With enough motivation and a hard enough look, he believes you can always find something.

When Is Security "Good Enough"?

Given that perfect security is not possible, the host asks how engineers should decide when they have done enough. Dahse compares it to securing a house. Closing windows and doors will not stop a highly skilled, well-funded attacker, but it is basic hygiene you should have. The difference with software is that you add new windows and doors every day as you ship features, so the basics require automation.

His suggested process has a few steps. Start with an initial assessment of where you stand today, using professionals or a tool, and fix the most critical issues. More importantly, make sure new code does not add more vulnerabilities, or more technical debt and quality problems that later become security issues. Automation is key to this. After a quarter or so, run the assessment again, ideally finding that you shipped features without slowing down and that your security posture improved. He calls it "a never ending story." New developments like LLMs and prompt injection mean developers building on LLMs or calling their APIs have to ask new questions, "and the next thing will come."

In a closing question, the host asks which programming language he considers most secure. Dahse says newer languages tend to be more secure because they learned from the mistakes of older ones, and names Go as a good example. He adds that older languages keep evolving, and he considers Java, which is common in enterprises, quite secure to use.