Hi, I'm Grant
RSS FeedI'm Grant Totinov, a software engineer with a Master's degree in Computer Engineering. I'm a member of the .NET Foundation and a Microsoft Q&A Community Champion, and I contribute to open source projects such as ASP.NET Core, Spectre.Console and Tiptap.
This blog is where I write up what I learn along the way, with links to the pull requests and specs behind each post.
Open source projects
AI Exporter
A free browser extension that saves your ChatGPT, Claude, Gemini and twelve other AI chats as PDF, Word, Markdown, Excel and five more formats, images included. Everything happens locally in your browser.
PixelForge
An MIT image processing library for .NET 8 and newer, built on SkiaSharp. It has the same Load, Mutate and Save API as ImageSharp, needs no license key, and comes with safe defaults for user uploads.
Recent Posts
Three ways to say [Authorize], one way to check it: fixing ASP.NET Core's security-metadata guard
ASP.NET Core routing refuses to run an endpoint whose authorization metadata nothing will enforce. That guard only recognized one of the three kinds of authorization metadata. Notes on PR #67742: where the gap was, a short-circuited endpoint that served 200 without authentication on .NET 10, and the fix.
POST in, DELETE out: antiforgery and HttpMethodOverride in ASP.NET Core
UseAntiforgery() validates tokens only for POST, PUT and PATCH. Put form-field HttpMethodOverride in front of it and a cross-site POST becomes a DELETE that is never checked. Notes on PR #66772 and docs PR #37224: why the method list is what it is, a working repro on .NET 10, and what we documented.
Five minutes on the login page: an expired passkey challenge shouldn't be a 500
If a user left the login page open for more than five minutes and then signed in with a passkey, ASP.NET Core Identity threw instead of returning SignInResult.Failed. Notes on PR #67539: where the challenge actually lives, why it expires, and where to draw the line between a failed sign-in and a programming error.
List`1[System.Int32] is not a style: escaping interpolated values in Spectre.Console markup
MarkupInterpolated escaped string arguments but passed every other object through untouched, so anything whose ToString() contained square brackets crashed the markup parser. That includes List<int> and int[]. Notes on spectre.console PR #2118: the fix, the IFormattable trade-off, and what still breaks.