You just inherited a legacy ASP.NET application. Hundreds of controllers, dozens of Entity Framework contexts, Razor views everywhere. The team that built it left six months ago. Your job: get it production-ready.
The first question your tech lead asks is “has anyone done a security review?” The answer is no. It’s never been audited.
Going through every controller and every view manually would take weeks. But Claude Code can give you a solid first pass in minutes. Not a replacement for a real penetration test — but a way to find the obvious vulnerabilities before someone else does.
The prompt that finds real issues
The key to a useful security audit with Claude Code is being specific. Don’t just ask “find security issues.” That gives you vague, generic advice. Instead, tell it exactly what to look for:
“Scan this codebase for OWASP Top 10 vulnerabilities. Focus on: SQL injection (especially raw SQL in Entity Framework), XSS in Razor views, missing authorization attributes on controllers, insecure deserialization, and hardcoded secrets. Show me the exact file, line, and a fix for each issue.”
That prompt works because it gives Claude Code a checklist. It will methodically go through your code, file by file, and flag concrete issues with line numbers. I’ve used this on three different legacy codebases now, and it consistently finds things that manual reviews missed.
You can also scope it down to a specific area:
“Review all controllers in /Controllers/Admin/ for missing [Authorize] attributes and any raw SQL queries.”
Smaller, focused prompts tend to be more thorough than asking it to review everything at once.
SQL injection in Entity Framework
This is the most common vulnerability I see in legacy .NET apps. Developers use Entity Framework but bypass it with raw SQL — and forget to parameterize:
// DON'T: vulnerable to SQL injection
public async Task<List<Product>> SearchProducts(string searchTerm)
{
var sql = "SELECT * FROM Products WHERE Name LIKE '%" + searchTerm + "%'";
return await _context.Products
.FromSqlRaw(sql)
.ToListAsync();
}
Claude Code flags this immediately. It spots the string concatenation inside FromSqlRaw and knows that’s a textbook injection vector. Here’s the fix it suggests:
// DO: parameterized query
public async Task<List<Product>> SearchProducts(string searchTerm)
{
return await _context.Products
.FromSqlInterpolated($"SELECT * FROM Products WHERE Name LIKE {'%' + searchTerm + '%'}")
.ToListAsync();
}
Or better yet, skip raw SQL entirely:
// BEST: use LINQ
public async Task<List<Product>> SearchProducts(string searchTerm)
{
return await _context.Products
.Where(p => p.Name.Contains(searchTerm))
.ToListAsync();
}
Claude Code doesn’t just find the vulnerability — it understands the Entity Framework API well enough to suggest the right fix for your version of EF Core.
XSS in Razor views
Cross-site scripting in Razor views usually comes from developers explicitly bypassing the built-in encoding. Razor encodes output by default, so you have to go out of your way to create an XSS vulnerability. But people do:
@* DON'T: renders raw HTML from user input *@
<div class="user-comment">
@Html.Raw(Model.Comment)
</div>
Or in Blazor:
@* DON'T: same problem in Blazor *@
<div class="user-bio">
@((MarkupString)user.Biography)
</div>
Claude Code catches both patterns. It knows that Html.Raw and MarkupString bypass encoding, and it checks whether the data source is user-controlled. If the data comes from a database field that users can edit, it flags it.
The fix depends on your use case. If you need to allow some HTML (like a rich text editor), Claude Code suggests using a sanitization library:
// Sanitize HTML before storing
using Ganss.Xss;
var sanitizer = new HtmlSanitizer();
var safeHtml = sanitizer.Sanitize(userInput);
If you don’t need HTML at all, just remove the Html.Raw and let Razor’s default encoding handle it.
Authentication and authorization gaps
Missing [Authorize] attributes are surprisingly common. A developer creates an admin controller, forgets the attribute, and suddenly anyone can access it:
// DON'T: no authorization on admin endpoints
[ApiController]
[Route("api/[controller]")]
public class AdminSettingsController : ControllerBase
{
[HttpPost("update-config")]
public IActionResult UpdateConfiguration(ConfigDto config)
{
// Anyone can call this
_configService.Update(config);
return Ok();
}
}
Claude Code scans every controller and flags the ones missing authorization. It also catches subtler issues:
// DON'T: Authorize on class, but AllowAnonymous on sensitive endpoint
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
[AllowAnonymous] // Mistake — exposes user data
[HttpGet("{id}")]
public async Task<IActionResult> GetUser(int id)
{
var user = await _userService.GetById(id);
return Ok(user);
}
}
It also reviews your authentication configuration. Insecure cookie settings, missing HTTPS enforcement, weak password policies — Claude Code flags these in your Program.cs:
// DON'T: insecure cookie settings
builder.Services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = false; // Should be true
options.Cookie.SecurePolicy = CookieSecurePolicy.None; // Should be Always
options.Cookie.SameSite = SameSiteMode.None; // Risky without Secure
});
Beyond OWASP: .NET-specific issues
Claude Code knows the .NET ecosystem well enough to catch framework-specific vulnerabilities that generic security scanners miss.
BinaryFormatter deserialization — this is a known remote code execution vector that Microsoft has deprecated, but it still shows up in legacy code:
// DON'T: BinaryFormatter is a security risk
var formatter = new BinaryFormatter();
var obj = formatter.Deserialize(stream); // Remote code execution
Claude Code flags this and suggests System.Text.Json or JsonSerializer as safe alternatives.
Debug endpoints left enabled — Swagger UI, developer exception pages, and diagnostic endpoints that should never reach production:
// DON'T: this should be behind an environment check
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI();
Hardcoded secrets — connection strings, API keys, and passwords sitting in appsettings.json or worse, directly in code:
// DON'T: secrets in source code
var connectionString = "Server=prod-db;Database=App;User=sa;Password=P@ssw0rd123;";
Claude Code catches these and recommends Azure Key Vault, user secrets for development, or environment variables.
Honest limitations
I want to be upfront about what Claude Code can’t do for security.
It doesn’t run your code. Claude Code performs static analysis only. It reads your source files and reasons about them. It can’t detect runtime-only vulnerabilities — race conditions, timing attacks, or issues that only appear under specific server configurations.
It may flag false positives. Sometimes it marks code as vulnerable when there’s actually a validation layer upstream that makes it safe. You still need to evaluate each finding in context.
It doesn’t replace penetration testing. A real pentest involves running the application, probing endpoints, testing authentication flows, and trying actual exploits. Claude Code gives you a first pass, not a final verdict.
It can miss things. Complex vulnerability chains that span multiple services, or issues hidden behind layers of abstraction, might not get caught. Custom middleware that introduces vulnerabilities can also slip through if the logic is complex enough.
Think of Claude Code as your first-pass security reviewer. It catches the low-hanging fruit — and in most legacy codebases, there’s a lot of low-hanging fruit.
Start here
Try this prompt on your own codebase today:
“Review this codebase for security vulnerabilities. Check for: (1) SQL injection — any raw SQL or string concatenation in database queries, (2) XSS — any use of Html.Raw or MarkupString with user-controlled data, (3) missing [Authorize] attributes on controllers that handle sensitive data, (4) hardcoded connection strings or API keys, (5) use of deprecated/insecure APIs like BinaryFormatter. For each finding, show the file path, the vulnerable code, and a specific fix.”
Run it on your most critical project first. You’ll probably be surprised by what it finds — and relieved that you caught it before production.