DotnetAngularDoctor.Tool
0.5.1
dotnet tool install --global DotnetAngularDoctor.Tool --version 0.5.1
dotnet new tool-manifest
dotnet tool install --local DotnetAngularDoctor.Tool --version 0.5.1
#tool dotnet:?package=DotnetAngularDoctor.Tool&version=0.5.1
nuke :add-package DotnetAngularDoctor.Tool --version 0.5.1
DotnetAngularDoctor
A command line tool that reads an ASP.NET Core + Angular repository and finds the configuration mistakes that make the application work locally but fail after deployment, or fail as soon as the frontend is wired to the backend.
Status: version 0.5, static analysis only. The tool reads files. It never modifies your project, never runs your code and never talks to the network. See Safety.
The problem
The settings that decide whether a full stack app works are spread over many files, and each file looks correct on its own:
| Setting | Lives in |
|---|---|
| CORS policy, cookie options, SignalR hubs, SPA fallback | Program.cs / Startup.cs |
| Backend urls | Properties/launchSettings.json |
| Dev server port, build output | angular.json |
| Request forwarding | proxy.conf.json / .js / .ts |
| IIS hosting, WebSockets | web.config |
withCredentials |
Angular services and interceptors |
Only the combination is wrong: AllowAnyOrigin() next to AllowCredentials(), a proxy that points at
port 7240 while Kestrel listens on 7042, a hub route that the proxy does not forward, a SPA fallback
that points at index.html while Angular writes wwwroot/browser/index.html. Each of these costs hours
of bisecting files. This tool collects the settings, compares them, and tells you what does not line up.
Example
$ dotnet-angular-doctor scan --path samples/BrokenProxyProject
Dotnet Angular Doctor
version 0.5.1
Project discovery
-----------------
✔ ASP.NET Core project detected
Only one ASP.NET Core project was found: src/MyApi/MyApi.csproj (web SDK ..., depth 2).
✔ Angular project detected
Only one Angular project was found: src/web/angular.json (angular.json, @angular/core ...).
✔ Program.cs detected
src/MyApi/Program.cs
✔ launchSettings.json detected
https://localhost:7042, http://localhost:5042
✔ angular.json detected
✔ Angular proxy detected
src/web/proxy.conf.json
Rules executed: 14 of 14
Diagnostics
-----------
⚠ FSD005 Angular proxy target does not match the ASP.NET Core launch URL
File: src/web/proxy.conf.json
Line: 2
Confidence: High
The Angular proxy forwards /api to https://localhost:7240, which is not one of the urls
the backend starts on.
Why it matters:
During development every proxied call ends in a connection error or a 502, which usually
looks like a broken API rather than a configuration mismatch.
Evidence:
Proxy target: https://localhost:7240
Backend launch urls: https://localhost:7042, http://localhost:5042
Suggested fix:
Point the proxy target at one of the launch urls, for example 'https://localhost:7042',
or change applicationUrl in launchSettings.json so both sides agree.
Summary
-------
Errors: 0
Warnings: 1
Information: 0
Scan completed in 118 ms.
Every finding carries: what, why it matters, which file, which line, the current value, the expected value, a severity, a confidence level and a text only suggested fix.
Install
From NuGet
dotnet tool install --global DotnetAngularDoctor.Tool
dotnet-angular-doctor scan --path /path/to/your/repo
Update or remove it:
dotnet tool update --global DotnetAngularDoctor.Tool
dotnet tool uninstall --global DotnetAngularDoctor.Tool
Requirements: the .NET 8 runtime or newer. The tool targets net8.0, so it runs on .NET 8, 9 and 10.
From source
git clone https://github.com/mohamedanter1996/DotnetAngularDoctor.git
cd DotnetAngularDoctor && dotnet pack src/DotnetAngularDoctor.Cli -c Release
dotnet tool install --global --add-source ./artifacts/package DotnetAngularDoctor.Tool
Prefer not to install anything? Run it straight from the repository:
dotnet run --project src/DotnetAngularDoctor.Cli -- scan --path /path/to/your/repo
Usage
dotnet-angular-doctor scan
dotnet-angular-doctor scan --path ./MySolution
dotnet-angular-doctor scan --path ./backend-repo --path ./frontend-repo
dotnet-angular-doctor scan --format json
dotnet-angular-doctor scan --format sarif --output results.sarif
dotnet-angular-doctor scan --severity warning
dotnet-angular-doctor scan --rule FSD001 --rule FSD005
dotnet-angular-doctor scan --exclude-rule FSD004
dotnet-angular-doctor scan --fail-on-error
dotnet-angular-doctor list-rules
dotnet-angular-doctor skill
dotnet-angular-doctor --version
Options
| Option | Default | Meaning |
|---|---|---|
--path, -p |
. |
Directory to scan. Point it at the repository root or the solution folder. Repeatable: pass it once per repository when the backend and the frontend are separate repositories. |
--format, -f |
text |
text for humans, json for other tools, sarif for GitHub code scanning and IDEs. Machine formats go to standard output on their own. |
--output, -o |
(stdout) | Write the report to this file. json and sarif only. The only file a scan ever creates. |
--severity, -s |
information |
Lowest severity to report: information, warning, error. |
--rule, -r |
(all) | Run only these rule ids. Repeatable. |
--exclude-rule, -x |
(none) | Never run these rule ids. Repeatable. |
--fail-on-error |
off | Let findings decide the exit code (see below). |
--verbose, -v |
off | Print analysis notes, the parsed files and full stack traces. |
--ascii |
auto | Plain ASCII marks instead of ✔ ○ ✖. Chosen automatically when the output is redirected, so hosts that read a legacy code page - the Visual Studio Package Manager Console among them - stay readable. |
--help |
Full help for any command. |
Separate repositories
The backend and the frontend do not have to live in one repository, and they may each have their own solution.
| Layout | Command |
|---|---|
| One repository | scan --path ./MyApp |
| Two repositories under one folder | scan --path ./work - both are found, because a folder that is not itself a repository is treated as a workspace |
| Two repositories that share nothing, for example different drives | scan --path D:/backend --path E:/frontend |
Everything a rule compares across the two halves - the proxy target against the launch url, the hub route against the proxy contexts, the Angular origin against the CORS policy - works the same way, because the rules read one merged model and never touch the file system themselves.
Reported paths are relative to the deepest folder that contains every scanned path, so the two repos
show up as backend/... and frontend/.... Directories that are repositories of their own are only
skipped when the scanned folder is itself a repository, where they can only be a worktree or a
submodule.
Exit codes
| Code | Meaning |
|---|---|
0 |
No error found, or findings were reported without --fail-on-error. |
1 |
Warnings only. Requires --fail-on-error. |
2 |
At least one error. Requires --fail-on-error. |
3 |
The tool failed, or no project could be analyzed under the given path. |
Documented default: without --fail-on-error a scan that finds problems still exits 0, so running
the tool locally never breaks a shell script. Code 3 is returned regardless of the flag, because that
means the tool could not do its job.
JSON output
The JSON contract is stable: property names never change inside a major version, and new versions may only add fields.
{
"toolVersion": "0.5.1",
"scannedPath": "samples/BrokenCorsProject",
"durationMilliseconds": 132,
"success": true,
"failureMessage": null,
"projects": { "dotnetProjects": 1, "angularProjects": 1 },
"rules": { "registered": 12, "executed": 12 },
"summary": { "errors": 1, "warnings": 0, "information": 0 },
"diagnostics": [
{
"ruleId": "FSD001",
"severity": "Error",
"category": "CORS",
"title": "AllowAnyOrigin cannot be combined with AllowCredentials",
"filePath": "src/MyApi/Program.cs",
"lineNumber": 9,
"message": "The CORS policy 'AllowAngular' calls both AllowAnyOrigin() and AllowCredentials().",
"whyItMatters": "Credentialed requests require explicit trusted origins.",
"evidence": "AllowAnyOrigin() ... AllowCredentials()",
"suggestedFix": "Replace AllowAnyOrigin with WithOrigins and provide the Angular origin.",
"confidence": "High",
"documentationUrl": "https://github.com/mohamedanter1996/DotnetAngularDoctor/blob/main/docs/diagnostic-rules.md#fsd001"
}
],
"notes": []
}
Rules
Fourteen rules ship in version 0.5. Full reference with a broken example, a correct example and the uncertain cases: docs/diagnostic-rules.md.
| Rule | Category | Title |
|---|---|---|
| FSD001 | CORS | AllowAnyOrigin cannot be combined with AllowCredentials |
| FSD002 | CORS | Angular origin is not included in the configured CORS origins |
| FSD003 | Cookies | SameSite None cookie may be sent without a secure policy |
| FSD004 | Cookies | Cookie authentication detected but Angular credentials configuration was not found |
| FSD005 | Proxy | Angular proxy target does not match the ASP.NET Core launch URL |
| FSD006 | SignalR | SignalR hub route is not included in the Angular proxy |
| FSD007 | SignalR | SignalR service registration and hub mapping are inconsistent |
| FSD008 | SPA | SPA fallback route was not found |
| FSD009 | SPA | SPA fallback file may not match the Angular build output |
| FSD010 | SignalR | Angular proxy WebSocket support may be disabled |
| FSD011 | Middleware | Authentication or authorization middleware order may be invalid |
| FSD012 | Middleware | CORS middleware order may prevent the policy from being applied |
| FSD013 | Middleware | HTTPS redirection may break the calls the frontend makes over HTTP |
| FSD014 | Proxy | Angular environment points at a backend url that is not a launch url |
Confidence is part of every finding. When the analysis cannot prove something - a value that comes from
configuration, a setting that depends on the environment, a proxy.conf.js that would have to be
executed to be understood - the tool reports a warning with lower confidence instead of a certain error.
CI/CD
GitHub Action
The repository ships an action, so findings land in the Security → Code scanning tab with the file, the line and the suggested fix, and appear as annotations on the pull request that introduced them.
name: configuration
on: [push, pull_request]
permissions:
contents: read
security-events: write # required to upload the SARIF log
jobs:
doctor:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: mohamedanter1996/DotnetAngularDoctor@v1
Every input, with its default:
- uses: mohamedanter1996/DotnetAngularDoctor@v1
with:
path: '.' # directory to scan
severity: 'information' # information | warning | error
rules: '' # e.g. 'FSD001 FSD005', empty runs all
exclude-rules: '' # e.g. 'FSD004'
fail-on-error: 'true' # fail the step on findings
sarif-file: 'dotnet-angular-doctor.sarif' # where the log is written
upload-sarif: 'true' # send it to code scanning
tool-version: '' # pin the tool, empty takes the latest
skip-install: 'false' # true uses a doctor already on PATH
job-summary: 'true' # readable report in the job summary
Outputs: sarif-file, exit-code, errors, warnings.
Code scanning upload is free on public repositories; private repositories need GitHub Advanced
Security. Set upload-sarif: false to keep the log as a plain artifact instead.
Plain commands
- name: Check full stack configuration
run: |
dotnet tool install --global DotnetAngularDoctor.Tool
dotnet-angular-doctor scan --fail-on-error
Only fail on errors and keep warnings visible:
- name: Check full stack configuration (errors only)
run: dotnet-angular-doctor scan --severity warning --fail-on-error
Keep the machine readable report as a build artifact:
- name: Configuration report
run: dotnet-angular-doctor scan --format json --output doctor-report.json
- uses: actions/upload-artifact@v7
with:
name: doctor-report
path: doctor-report.json
Any other SARIF consumer, without the action:
- run: dotnet-angular-doctor scan --format sarif --output results.sarif
- uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: results.sarif
Azure Pipelines:
- script: dotnet-angular-doctor scan --fail-on-error
displayName: Check full stack configuration
Pre-commit hook (.git/hooks/pre-commit):
#!/bin/sh
dotnet-angular-doctor scan --severity error --fail-on-error || exit 1
Using it with an AI assistant
The tool finds the mismatch; it never edits a file. An AI coding assistant is the other half - it can apply the fix once it knows exactly what to change. The tool ships the instructions that make that reliable, so they always match the installed version:
dotnet-angular-doctor skill
That prints a skill document - how to scan, the JSON contract, how to weigh confidence against severity,
and which "fixes" are forbidden because they silence a finding by weakening the application.
--fixes prints the per rule recipes.
For an assistant that loads skills from a folder:
dotnet-angular-doctor skill --install .claude/skills
For one that takes an upload instead - the Skills page of the Claude apps, for example - write the
archive it expects, SKILL.md at the root and nothing else in it:
dotnet-angular-doctor skill --package dotnet-angular-doctor-skill.zip
For any other assistant, one line in whatever instruction file it already reads - AGENTS.md,
CLAUDE.md, .github/copilot-instructions.md, a Cursor rule - is enough:
Before changing CORS, dev proxy, SignalR, SPA fallback or middleware order, run
`dotnet-angular-doctor skill` and follow the instructions it prints.
Full setup, including what the skill tells the assistant to do and refuse: docs/using-with-ai-agents.md.
Sample projects
The samples folder contains one healthy repository and five deliberately broken ones. They
are analysis fixtures, not runnable applications, and are excluded from the solution on purpose.
dotnet run --project src/DotnetAngularDoctor.Cli -- scan --path samples/HealthyProject
dotnet run --project src/DotnetAngularDoctor.Cli -- scan --path samples/BrokenSignalRProject
| Sample | Expected findings |
|---|---|
HealthyProject |
none |
BrokenCorsProject |
FSD001 |
BrokenCookieProject |
FSD003, FSD004 |
BrokenProxyProject |
FSD005 |
BrokenSignalRProject |
FSD006 |
BrokenSpaFallbackProject |
FSD009 |
Safety and privacy
The tool works on your source code, so it is deliberately restricted:
- It only reads files. No file of your project is created, changed or deleted.
- There is no auto fix. Suggested fixes are text, so every edit stays yours to review.
- No network access. Nothing is uploaded, no cloud service is used, no telemetry is collected.
- It never executes anything from the analyzed project: no
npm, nodotnet, no build, no scripts. - It never restores or loads the analyzed project's packages. The C# analysis is syntax only.
- Secrets are redacted before they enter the report: passwords, keys, tokens, connection strings and
similar values from
appsettings*.jsonand environment variables are replaced with***REDACTED***. A test asserts that no secret from the sample project can reach text or JSON output. - Suggested fixes never weaken security: the tool will not tell you to allow any origin, disable TLS validation or drop a secure cookie policy.
Contributing
Bug reports, rule ideas and pull requests are welcome. Start with CONTRIBUTING.md, and read docs/creating-rules.md if you want to add a rule - it takes one class and one test file, and the engine never has to change.
dotnet build DotnetAngularDoctor.sln
dotnet test DotnetAngularDoctor.sln
Roadmap
Next up are opt in runtime checks (reachability, SignalR negotiate, redirects, cookie round
trips, TLS certificates, WebSocket dial, security headers). Everything that is deliberately out of scope
today is listed in docs/roadmap.md.
Documentation
- docs/architecture.md - how the projects fit together and why
- docs/diagnostic-rules.md - the rule reference
- docs/creating-rules.md - how to add a rule
- docs/using-with-ai-agents.md - wiring the tool into an AI assistant
- docs/roadmap.md - what comes next and what is out of scope
- PLAN.md - the plan the first version was built from
License
MIT.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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.