Type Level Security: The future of secure AI code generation?
4 juin 2026
0 minutes de lectureIntro
With code being written (& generated) faster than ever before, there is the unfortunate side effect that security vulnerabilities are also coming faster than ever before. Asking your LLM not to include security vulnerabilities in its code doesn't always work. It is becoming clear that the way software is built today, manually or with assistance, is insufficient when it comes to reliably, consistently, and provably writing secure code.
Rust's rise in popularity has shown that it is possible to completely remove entire vulnerability classes in a reasonable and ergonomic way. Rust has effectively eliminated all (most) memory corruption vulnerabilities at compile time. Why, then, should we limit ourselves to this class of vulnerabilities?
In this post, I will discuss how it can be possible to write code in such a way that web application security vulnerabilities are uncompilable (or un-type-checkable), how having secure-by-design libraries or well-placed library wrappers can completely stop entire classes of vulnerabilities from being written, either manually or by an LLM. I will show code patterns in both Python and Rust that could be used to eliminate vulnerability classes.
Why types
When used properly, type systems can be incredibly powerful tools. They allow you to codify the invariants of a system, that is, all of the rules, properties, and relationships of every piece of data in your program. Rather than leaving a helpful comment or performing run-time assertions (that may only be hit when your first customer tries to do something important), you can ensure that every single caller of your add The function is only passing two int types, in every case, across the whole codebase.
Anecdotally, code I write with extremely strict types contains considerably fewer runtime bugs than code I write without, as I am forced to reason about every parameter, every piece of data, and every input. Codified appropriately, I make considerably fewer mistakes, and my code then only has business logic bugs rather than 'oops, I passed a string to an integer addition function'.
Many security vulnerabilities are just a specific type of bug, and if you take my above assertion to be true, then many such bugs should be solvable using a sufficiently flexible type system.
Trusted types
This post is, in part, inspired by the Trusted Types web API. This API effectively mitigates most forms of DOM XSS by ensuring that XSS injection sinks accept only known-safe values, such as those sanitized by an appropriate XSS sanitizer. This API can be further locked down using CSP to require the use of the Trusted Types API, throwing a TypeError if it is not used.
A similar mechanism is in use by the Linux Kernel with the __user macro, which ensures that pointers from userland are handled appropriately.
In the following sections, we will look at the generalization of this technique and how you can apply it to arbitrary security vulnerability classes.
Solving IDOR
Insecure Direct Object Reference (IDOR) is a pernicious vulnerability stemming from a lack of authentication and/or authorization checks when performing API actions. The classic example of this is an API endpoint that takes an incrementing user ID parameter and returns user data, but when changing the user ID value it returns data from a different user, without appropriate authorization checks.
Statistically, you, the reader, likely use Python, so we will first explore this vulnerability and how to solve it in Python using only type hints.
In Python
We start with a vulnerable example:
This is a pretty standard-looking API call: you pass in an integer user ID, query the database, and return the result as the response. But whoops! We forgot to add any authentication or authorization checks; any user can request any other user's balance (assuming they have the ID), a classic IDOR. We receive the vulnerability report, we pay the researcher their bounty, and we update the function thus:
Perfect, the user data is safe. But that's a frightfully easy mistake to make. Forgetting one auth check on one API endpoint can lead to significant impacts. Now, let us look at the same endpoint, but using the type system to make sure this vulnerability doesn't happen again:
There is a fair bit more code here, but in the grand scheme of things, it would be swallowed up by your authentication and authorization code anyway.
The main change in this new code is that we never pass around basic types (like the user ID as an int in the previous examples). Input data is abstracted away into an opaque class, and you can only access it by proving that you've already done the authentication and authorization steps. By ensuring that your UncheckedUserID class cannot do anything useful (like being used in a SQL expression), your type checker will reliably error if you've forgotten to perform the authentication check to extract the 'real' user_id value.
Python, being as dynamic as it is, means you could technically bypass the check and access to the internal value without providing a valid AuthenticationGuard, but you'd hope this would be caught in PR review as undesirable code. Other languages, such as Rust, can provide much stronger guarantees, meaning that even with ugly code, you cannot directly access the internal value of the wrapper class. Obviously, the code is still running, so you can take extreme measures like dumping the instance's memory, but if you just want to ensure your auth is consistent, why would you do this at all?
In Rust
Visibility specifiers in Rust provide strong control over which code can access the wrapped user_id value, making this an even more reliable way to ensure authentication and authorization checks. In the example below, we use Axum with an extractor to ensure that the input value is correctly deserialized into our wrapper safety class.
Practicality
Obviously, this method for mitigating security vulnerabilities requires significant buy-in, especially in a more established software project. Coding infrastructure would be necessary to ensure these patterns are consistently followed. The two main methods to achieve this would be wrapper libraries or custom linter rules.
Take the Python case, for example. As an organization, you could require the MyOrgFastAPI library, a lightweight wrapper over FastAPI, and ensure that all API endpoints use custom types that implement these security requirements. No endpoint should be allowed to take a str argument; it must be some form of UncheckedString.
Alternatively, if you don't want to maintain a custom wrapper library, these rules could be implemented at the linter level. Whilst this does not provide feedback to a developer as early, it can still provide the same safety net by banning basic types.
Whichever way you achieve it, it would ensure that every single case is handled properly. This naturally extends the code pattern into protecting against all sorts of vulnerabilities:
An
UncheckedStringmust be sanitized appropriately to get out a raw string for returning to the user, mitigating XSSAn
UncheckedStringcannot be concatenated onto an SQL query, mitigating classic SQL vulnerabilitiesAn
UncheckedStringcannot be concatenated onto a command string, mitigating command injection vulnerabilities.
The list goes on.
Such patterns would also apply equally to human developers and AI agents. Asking an AI agent to ensure authentication everywhere may not be 100% reliable, but enforcing authentication at the compilation or linting stage can be extremely powerful.
Conclusion
Implementing security like this at the type level can be an incredibly powerful tool for both human developers and AI agents equally. Whilst there may be some initial overhead, especially on existing software projects, the benefits can be significant, as entire classes of vulnerabilities can be wiped out.
How you handle your data can be very project-specific, but thinking about it early can pay dividends for secure code.
WHITEPAPER
The AI Security Crisis in Your Python Environment
As development velocity skyrockets, do you actually know what your AI environment can access?
