ZainUlAbiddin.SecAuditor
1.0.0
dotnet tool install --global ZainUlAbiddin.SecAuditor --version 1.0.0
dotnet new tool-manifest
dotnet tool install --local ZainUlAbiddin.SecAuditor --version 1.0.0
#tool dotnet:?package=ZainUlAbiddin.SecAuditor&version=1.0.0
nuke :add-package ZainUlAbiddin.SecAuditor --version 1.0.0
SecAuditor
A Roslyn-based security auditor for ASP.NET Core APIs. It scans a real .sln/.csproj
for common OWASP-relevant misconfigurations - missing [Authorize], JWT/CORS
misconfig, hardcoded secrets, missing rate limiting, raw-SQL injection risk - and
can optionally ask Claude to explain each finding in plain language with a concrete
fix.
Rules are deterministic (real AST analysis via Roslyn, not string matching, not regex-over-everything); AI is only used to narrate findings the rule engine already found, never to invent them.
Why
Most static analyzers for .NET are generic (Snyk, SonarQube) - they don't speak
ASP.NET Core's specific footguns (AllowAnyOrigin() + AllowCredentials(),
FromSqlRaw vs FromSqlInterpolated, ValidateIssuer = false) and they don't
explain why something matters or how to fix it in your code. SecAuditor is
narrow on purpose: seven checks, each backed by a real vulnerable/safe pair in the
test fixture, each with a specific, actionable fix - and each validated against a
real production ASP.NET Core codebase, not just the fixture (see Verification).
What it can scan
Built for: ASP.NET Core Web API projects (net6.0+). The checks assume common ASP.NET Core idioms:
- Controllers with
[Authorize]/[AllowAnonymous]/[HttpPost]etc. (SEC001) - JWT auth via
Microsoft.AspNetCore.Authentication.JwtBearer(SEC002) - CORS via
AddCors/AllowAnyOrigin/AllowCredentials(SEC003) appsettings*.jsonconfiguration files (SEC005)- Rate limiting via
AddRateLimiter(SEC006) - EF Core (
FromSqlRaw/ExecuteSqlRaw) or ADO.NET (SqlCommand/OleDbCommand/NpgsqlCommand) (SEC007)
Works on any C# project, ASP.NET Core or not: SEC004 (hardcoded secrets in source) doesn't depend on any ASP.NET Core API, so it's useful on console apps, class libraries, Windows services, anything.
Not the target, and will just report nothing (not false positives): WinForms/WPF desktop apps, non-web console apps, class libraries with no controllers/auth/CORS/SQL of their own - the ASP.NET-specific rules simply have nothing to check and stay silent. It's safe to point the tool at them; it just won't find much.
Untested, best-effort: classic .NET Framework (net48 etc.) projects. Project
loading goes through MSBuildWorkspace, which can often open older-style .csproj
files too, but this hasn't been verified against one.
Before scanning: run dotnet restore on the target project/solution first.
MSBuildWorkspace evaluates the project file, so a project that's never been
restored may load incompletely or spam workspace warnings to stderr (the scan
still runs - Roslyn's syntax trees don't need a successful compilation, just
valid project evaluation).
Point it at a .sln to scan every project in a solution at once, or at one
.csproj to scan just that project.
Install
git clone https://github.com/<your-username>/DotNetSecAuditor.git
cd DotNetSecAuditor
dotnet pack src/SecAuditor -c Release
dotnet tool install --global --add-source ./nupkg ZainUlAbiddin.SecAuditor
Requires the .NET SDK (10.0+; built and tested on 10.0.401). Check with dotnet --version.
To update after pulling new changes:
dotnet tool uninstall --global ZainUlAbiddin.SecAuditor
dotnet pack src/SecAuditor -c Release
dotnet tool install --global --add-source ./nupkg ZainUlAbiddin.SecAuditor
Run without installing
No need to pack/install just to try it out:
dotnet run --project src/SecAuditor -- path/to/YourApi.csproj
Usage
secauditor path/to/YourApi.csproj
secauditor path/to/YourSolution.sln --json report.json --md report.md --html report.html
secauditor path/to/YourApi.csproj --ai # requires ANTHROPIC_API_KEY
| Flag | Effect |
|---|---|
--json <path> |
Write findings as structured JSON |
--md <path> |
Write a Markdown report |
--html <path> |
Write a styled, self-contained HTML report |
--ai |
Ask Claude to explain each finding + suggest a fix. Silently skipped with a warning if ANTHROPIC_API_KEY isn't set - the tool never fails just because AI isn't configured. |
--fail-on <Severity> |
Exit 1 if any finding is at or above this severity (Low/Medium/High/Critical, or none). For CI gates - without this flag, exit code is always 0 when the scan itself succeeds (findings are informational, not a build failure by default). |
Use as a GitHub Action
Drop this into .github/workflows/security.yml in any ASP.NET Core repo to scan
every pull request, upload the report as a build artifact, and post (and update,
on re-push) a PR comment with the findings:
name: Security scan
on: [pull_request]
jobs:
secauditor:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: zainbinattaullah/DotNetSecAuditor@master
with:
path: src/YourApi/YourApi.csproj
fail-on: Critical # or High / Medium / Low / none
# ai: 'true' # needs env.ANTHROPIC_API_KEY set on this job from a secret
The action builds SecAuditor from source on each run (it isn't published to
NuGet.org), so no separate install step is needed - actions/checkout on your
repo plus the action reference above is enough. See action.yml for
all inputs/outputs, and .github/workflows/ci.yml in
this repo for a working example (it dogfoods the action against its own test fixture).
Rules
| ID | Check | Severity |
|---|---|---|
| SEC001 | Mutating controller action (POST/PUT/DELETE/PATCH) with no [Authorize] and no explicit [AllowAnonymous] |
High |
| SEC002 | TokenValidationParameters disabling ValidateIssuer/ValidateAudience/ValidateLifetime/ValidateIssuerSigningKey, or a JWT signing key built from a string literal |
Critical |
| SEC003 | CORS AllowAnyOrigin() - Critical if combined with AllowCredentials() (also an ASP.NET Core runtime error), Medium alone |
Critical / Medium |
| SEC004 | Hardcoded secret (password/API key/connection string) assigned to a string literal in C# | Critical |
| SEC005 | Hardcoded secret in appsettings*.json - matches both "ApiKey": "..."-shaped keys and Password=... embedded in a connection string value |
Critical |
| SEC006 | Project-wide: looks like a web API but no AddRateLimiter() anywhere |
Medium |
| SEC007 | FromSqlRaw/ExecuteSqlRaw/SqlCommand built from an interpolated or concatenated string instead of parameters |
Critical |
Every rule skips values that look like placeholders (CHANGE_ME_*, TODO, empty
strings), URLs, or natural-language sentences, so config templates and UI copy
don't drown real findings - see Rules/SecretValueHeuristics.cs.
Architecture
src/SecAuditor/
Analysis/ ProjectLoader (MSBuildWorkspace), AuditEngine (runs all rule kinds)
Rules/ ISecurityRule (per-file), IProjectRule (whole-project), IFileContentRule (non-C# files)
Ai/ IFindingExplainer + Claude-backed implementation
Reports/ JSON / Markdown / HTML writers
tests/SampleVulnerableApp/
A real ASP.NET Core Web API with one deliberately planted bug per rule, plus a
correctly-guarded counter-example for each - the project's own regression test.
Three rule interfaces because checks operate at three different granularities:
ISecurityRulejudges one C# file at a time (most rules)IProjectRuleneeds to see every file before it can decide (e.g. "is there a rate limiter anywhere in this project?")IFileContentRuleruns on files that are never part of the C# compilation at all (appsettings*.json)
Adding a rule means implementing one of these three interfaces and registering it
in Program.cs - nothing else changes.
Verification
Two layers of proof, not just "it compiles":
tests/SampleVulnerableAppplants exactly one bug per rule and one correctly-fixed counter-example per rule (an[Authorize]-guarded action, a parameterized SQL query, an[AllowAnonymous]action, etc.). Runningsecauditoragainst it reports exactly 9 findings with zero false positives on the guarded code:dotnet run --project src/SecAuditor -- tests/SampleVulnerableApp/SampleVulnerableApp.csprojRun against a real, unmodified production codebase. The fixture alone can only prove a rule fires on a bug planted to trigger it - it can't prove the rule stays quiet on real, messy code it was never designed around. Running an early version against a real ASP.NET Core API surfaced two genuine false positive shapes the fixture never had (a URL named
PasswordResetUrl, a UI message namedGenericForgotPasswordMessage- both matched on "name contains 'password'"), which is exactly whatRules/SecretValueHeuristics.csexists to fix. That real run also turned up a genuinely hardcoded Gmail app password and a predictable default reset password - the kind of finding a fixture can't manufacture on its own.
Contributing / adding a rule
- Pick the right interface (
ISecurityRulefor a per-file check,IProjectRulefor a whole-project check,IFileContentRulefor a non-C# file). - Implement it in
Rules/, register it inProgram.cs. - Add one vulnerable fixture and one correctly-fixed counter-example to
tests/SampleVulnerableAppand confirm the finding count in the README's verification command still matches.
Roadmap
- Live endpoint scanning (send test requests, check headers/rate limits at runtime)
- Full OWASP API Security Top 10 coverage
- Web dashboard with historical trend tracking
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|
First public release: 7 rules (missing [Authorize], JWT/CORS misconfig, hardcoded secrets in code and config, missing rate limiting, raw-SQL injection risk), JSON/Markdown/HTML reports, optional AI-generated explanations, --fail-on CI gate, and a reusable GitHub Action.