Using AI Code Generation Securely
Generated by https://deepai.org/.
As a security-minded person that writes code, it is important that the code I write is as secure as possible. This is especially important since the code-writing part of what I do is quite minimal - I spend most of my time on non-code-related security projects. With the new LLMs being offered up online I can actually get help writing the code, especially when it needs to be in another language I’m not used to programming in. This blog post does try to address that scenario with a few tips, with a focus on my approach.
I’d mentioned my approach to more than one person, and they all seemed either surprised or dropped into self-reflective mode saying something like “maybe I should try that” so I thought this was something interesting and worth sharing.
My Approach
I have a lot of experience writing code in C, some in Python, and that about covers it. There are quite a number of languages I can read and easily figure out what they mean, such as Java, Go, Ruby, Rust, and so on, but if I want to be comfortable I’d stick with C and Python. Immediately people hear C and try to steer me away from it, telling me it is full of security problems, and yes I am aware of those - I am even aware of some that a lot of people are not - so I feel like I could possibly spot a few problems before they occur in that language. Using that scenario with C, I decided to do some testing involving LLMs.
I am currently working on a project writing code that uses the LibOQS library. This is a cryptographic library that allows one to use quantum safe algorithms, including the ones recently tested and approved by NIST in their PQC (Post Quantum Cryptography) project. The library is written in C. So after reading up on how to use the library (and even downloading and exploring the source code itself) I wrote up some simple programming that used the library. I tried to make the code as secure as possible.
Next, I asked several LLMs for code that did more or less the same thing I had already done. I was looking for several things that would make my life easier. First, I wanted the code to be secure. Asking for the code in C is a decent way to check this as there are a fair number of potential pitfalls. Second, I wanted the code to be efficient and readable. If there were repeated sections that could be their own function, I’d want it implemented that way. The clearer the logic is laid out, the more readable the code is. And finally, and this is just me, I’d prefer the code to be stylistically more similar to how I write code than something else. To make things readable for me I prefer my formatting a particular way, so if I could get that from the start, great.
Your mileage with vary between LLMs simply based upon what you are looking for in the code output. In my case, after trying several (including OpenAI’s ChatGPT and Google’s Gemini) I really preferred Anthropic’s Claude. Let me preface this by saying Claude was meeting the efficiency, readability, and style I preferred over the other LLMs, however none of them were delivering on the security part to the level I’d like. So I came up with a trick that between all of the LLMs Claude seemed to give me the best results. The trick was to take the output from one chat session or thread, and simply start a second chat session or thread but with some direction to make it more secure. This improved things a lot more. All of the LLMs did this reasonably well, but Claude seemed to take it a bit further with slightly better results.
Other Notes
There were a few times Claude quite pleasantly surprised me. I spotted an error in Claude’s first draft that past experience told me was probably going to cause a compiler error. I compiled it and sure enough it did, so I decided to see what Claude would make of it. So in a second chat I said I got an error which I pasted in, and then pasted in Claude own code to see how it handled things. The results were interesting.
My prompt: “I have a routine in c that I am trying to eliminate a compiler error. The error is as follows:
In function 'strncpy',
inlined from 'resolve_path' at config.h:44:9:
/usr/include/x86_64-linux-gnu/bits/string_fortified.h:95:10: error: '__builtin_strncpy' output may be truncated copying 4095 bytes from a string of length 4095 [-Werror=stringop-truncation
This points to the strncpy right after parent_path is defined.
The routine in question is attached.
// Secure path resolution
static int resolve_path(const char* base_path, const char* input_path, char* resolved_path) {
char temp_path[PATH_MAX];
// If input_path is absolute, use it directly
if (input_path[0] == '/') {
strncpy(temp_path, input_path, PATH_MAX - 1);
} else if (base_path) {
// Combine base_path with input_path
snprintf(temp_path, PATH_MAX, "%s/%s", base_path, input_path);
} else {
strncpy(temp_path, input_path, PATH_MAX - 1);
}
// Normalize path
char* real_path = realpath(temp_path, resolved_path);
if (!real_path) {
// If file doesn't exist yet (like for output files), verify parent directory
char parent_path[PATH_MAX];
strncpy(parent_path, temp_path, PATH_MAX - 1);
char* last_slash = strrchr(parent_path, '/');
if (last_slash) {
*last_slash = '\0';
if (!realpath(parent_path, resolved_path)) {
return -1;
}
// Reattach filename
strncat(resolved_path, "/", PATH_MAX - strlen(resolved_path) - 1);
strncat(resolved_path, last_slash + 1, PATH_MAX - strlen(resolved_path) - 1);
} else {
return -1;
}
}
return 0;
}"
And here is Claude’s reply:
"The compiler is warning about potential string truncation with strncpy. This is a common issue because strncpy doesn't guarantee null-termination if the source string length is greater than or equal to the size limit. Let's fix this by ensuring proper null-termination and using safer string handling.
Here's the corrected version:
// Secure path resolution
static int resolve_path(const char* base_path, const char* input_path, char* resolved_path) {
char temp_path[PATH_MAX];
// If input_path is absolute, use it directly
if (input_path[0] == '/') {
strncpy(temp_path, input_path, PATH_MAX - 1);
temp_path[PATH_MAX - 1] = '\0'; // Ensure null-termination
} else if (base_path) {
// Combine base_path with input_path
snprintf(temp_path, PATH_MAX, "%s/%s", base_path, input_path);
} else {
strncpy(temp_path, input_path, PATH_MAX - 1);
temp_path[PATH_MAX - 1] = '\0'; // Ensure null-termination
}
// Normalize path
char* real_path = realpath(temp_path, resolved_path);
if (!real_path) {
// If file doesn't exist yet (like for output files), verify parent directory
char parent_path[PATH_MAX];
size_t temp_len = strlen(temp_path);
if (temp_len >= PATH_MAX) {
return -1; // Path too long
}
memcpy(parent_path, temp_path, temp_len + 1); // Copy including null terminator
char* last_slash = strrchr(parent_path, '/');
if (last_slash) {
*last_slash = '\0';
if (!realpath(parent_path, resolved_path)) {
return -1;
}
// Reattach filename
size_t resolved_len = strlen(resolved_path);
if (resolved_len + 1 >= PATH_MAX) { // Check space for '/'
return -1;
}
strncat(resolved_path, "/", PATH_MAX - resolved_len - 1);
size_t remaining_space = PATH_MAX - strlen(resolved_path) - 1;
strncat(resolved_path, last_slash + 1, remaining_space);
} else {
return -1;
}
}
return 0;
}
Key changes made:
Added explicit null-termination after strncpy calls
Replaced the problematic strncpy for parent_path with memcpy that includes the null terminator
Added length checks before string operations
Pre-calculated string lengths and remaining buffer space to ensure safety
Added additional bounds checking to prevent buffer overflows
These changes should eliminate the compiler warning while maintaining or improving the security of the path resolution. The code now:
Explicitly handles null-termination
Checks for buffer overflow conditions
Uses safer string copying methods
Maintains proper bounds checking throughout
Let me know if you need any clarification on the changes or if you encounter any other issues!"
I wasn’t asking for security fixes, I was asking to correct a compiler error. This was a quite pleasant surprise, and while I did not completely and scientifically test each LLM with this type of scenario multiple times, Claude always came up with the better solutions, and often with very little “security checking” guidance.
Conclusion
I don’t know how a lot of people will take this, but from my perspective I have probably more confidence in AI and LLMs when it comes to code suggestions, particularly when you introduce this ever-so-slight feedback loop. I can’t speak to things like the future of AGI or anything else really, I mean it is still spitting out code that is a bit insecure here and there so I’d still check the results, but it does give me hope.
When I eventually release my LibOQS project, there will be a blog post about that, and I will simply let that code speak for itself. It will take a while before I deliver this as I am also using Claude to assist with the entire “build” portion of the process as well as the “distribute” part as well. It should be quite interesting!