Monday, November 10, 2025

Time to move away from classic captchas

Starting from January 2026, reCAPTCHA changes their rules. Site keys are migrated to Google Cloud and you have to provide billing information, otherwise captcha basically doesn't work after 10k monthly assessments (technically it works but it always succeed, meaning you are vulnerable).

Since reCAPTCHA is used on millions of large and small websites, I don't even imagine what it means. Many of these websites are possibly poorly maintained and their owners won't even notice.

We did some research some time ago, looking at possible alternatives. One of the important factors is captcha's compliance with accessibility. Classic captchas (including reCAPTCHA) provide two interfaces, with additional audio-based challenges that are supposed to be accessible. I always believed this is a wrong approach because it basically gives two completely different vectors of a possible misuse - depending on which interface is easier to bypass, an attacker can focus on one or the other.

Also, reCAPTCHA doesn't provide the audio interface in other languages, try Polish and you'll find that it speaks in English.

What we ultimately decided is a custom version of a Proof-Of-Work captcha. Instead of going with existing solutions, we came with our own. This gives us a 100% control on how difficult the computation is at the client side. There were some critical changes in how SHA256 is computed with the subtle.crypto, namely, despite it's async, it no longer goes back to the event loop each time you await it. The UI is not updated but the performance is much higher. You just adapt to this new behavior by raising the difficulty of the client-side work to be done.

Since it's 6 weeks remaining, take your time to inspect all your reCAPTCHA websites, consider either sticking to it or moving away. But do not let yourself wake up in January and find out that things changed without your awareness.

Thursday, October 16, 2025

KB5066835 went terribly wrong

Yesterday, on 15.10.2025, KB5066835 was released for Windows 11. And guess, what, it causes IIS and IIS Express to reject client connections.

This effectively not only stops people who develop using VS + IIS Express but seems that some websites are affected

kb5066835 breaks IIS Express - Developer Community

KB5066835 update causing IIS Service to not work - Microsoft Q&A

Localhost not working anymore after 2025-10 cumulative update Windows 11 - Microsoft Q&A

Localhost applications failing after installing "2025-10 Cumulative Update for Windows 11 Version 24H2 for x64-based Systems (KB5066835) (26100.6899)" - Stack Overflow

windows - Http 2 Protocol error connecting to localhost web sites - Server Fault

Visual Studio - IIS Express - Site won't run / connect / SSL error - Umbraco community forum

Most people suggest uninstalling the KB, however, if your Windows refuses it, try the workaround from the last link above:

  1. In the registry, navigate to: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\HTTP\Parameters
  2. Under the Parameters folder, right-click in the right-hand pane and select New > DWORD (32-bit) Value.
  3. Name the value EnableHttp2Tls and set its data to 0 (zero).
  4. Repeat the process to add another DWORD (32-bit) Value named EnableHttp2Cleartext and set its data to 0.
  5. Restart the machine.

Edit:Seems that it has been patched in a seemingly unrelated Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.439.210.0).

Thursday, May 15, 2025

OldMusicBox.ePUAP.Client.Core 1.25.5

I am proud to announce that the NET8 port of the OldMusicBox.ePUAP.Client is published at GitHub and Nuget.
Please find more details on the package home page.

Wednesday, March 26, 2025

Tests in the very same console app - compilation error in .NET Core

Having unit tests in the very same console app causes a compilation error:

Program has more than one entry point defined. Compile with /main to specify the type that contains the entry point.

Kind of unexpected, there's definitely a single Main.

There problem seems to be there for all these years, despite being first described back in 2017

The solution is alread provided in the link above, just add

<GenerateProgramFile>false</GenerateProgramFile>

to the *.csproj

A brave little cookie-footer

One of our apps contains a page and the page has a div. The div's class name is cookie-footer.

The div itself has nothing to do with actual cookies, it contains a content that user is supposed to see.

And what? Turns out Brave doesn't show that div. It just adds:

// user agent stylesheet
.cookie-footer {
    display: none !important;
}

What's bizzare, Brave doesn't add this always. We have the app on multiple domains, the user agent style is added on most of domains but not all of them!

Tried other variants:

  .cookie-footer  - blocked
  .cookiefooter   - blocked
  .cookie--footer - works
  .cookiee-footer - works
  .coookie-footer - works

Great times. It's not only the legal regulation that can block your content, it's also your browser heuristic.

Monday, March 17, 2025

A fairy tale of misusing the C# typesystem

Once upon a time in a kingdom far far away someone wrote a code that required two string arguments:

    public class Worker
    {
        public void DoWork( string name, string surname )
        {
            Console.WriteLine( $"{name} {surname}" );
        }
    }

All the people used the code for years without any issues:

    new Worker().DoWork( "john", "doe" );

Then, someone in a hurry did something bad which should never happen. Arguments were swapped in a call:

    new Worker().DoWork( "doe", "john" );

Consequences were severe.

The culprit was tried and expelled from the kingdom. The king called for his best wizards and asked them to do something so that it never ever happens in the future.

One of the wizards suggested that introducing types would make it clear of what real intentions of arguments are:

    public class Name
    {
        public Name( string value )
        {
            this.Value = value;
        }

        public string Value { get; set; }

        public override string ToString()
        {
            return this.Value;
        }
    }

    public class Surname
    {
        public Surname( string value )
        {
            this.Value = value;
        }

        public string Value { get; set; }
        public override string ToString()
        {
            return this.Value;
        }
    }


    public class Worker
    {
        public void DoWork( Name name, Surname surname )
        {
            Console.WriteLine( $"{name} {surname}" );
        }
    }

Initially people complained a bit but then started to get used to the new calling convention:

    new Worker().DoWork( new Name( "john" ), new Surname( "doe" ) );

The problems were gone. Everyone was happy.

Years passed, some wizards were gone, new wizards came to the kingdom. One of new wizards reviewed the code and came up with an idea.

- Why this convention is that awkward, why wrap strings in auxiliary types? - thought the wizard.

And he came up with an idea to add implicit conversion operators:

    public class Name
    {
        public Name( string value )
        {
            this.Value = value;
        }

        public string Value { get; set; }

        public override string ToString()
        {
            return this.Value;
        }

        public static implicit operator Name( string value )
        {
            return new Name( value );
        }
    }

    public class Surname
    {
        public Surname( string value )
        {
            this.Value = value;
        }

        public string Value { get; set; }
        public override string ToString()
        {
            return this.Value;
        }

        public static implicit operator Surname( string value )
        {
            return new Surname( value );
        }
    }
 

The new wizard was very proud of himself. He barely told anyone of his conversion operators so everyone else was still using the well established convention:

   new Worker().DoWork( new Name( "john" ), new Surname( "doe" ) );

But, since the conversion was now implicit, the wizard was able to make his own code shorter:

   new Worker().DoWork( "john", "doe" );

Years passed, new people arrived and then, someone in a hurry did something bad which should never happen. Arguments were swapped in a call:

    new Worker().DoWork( "doe", "john" );

Consequences were severe.

Was the culprit tried and expelled from the kingdom, same as last time?

Not really, the Wizard Council blamed the new wizard, the one who introduced both implicit conversion operators.

He was tried and expelled from the kingdom. His changes were reverted forever and everyone lived happily ever after.


This is based on a (almost) true story.

But can it run DOOM?

I barely repost news from elsewhere but this time this is extremely impressive.

It was announced that Dimitri Mitropoulos from the TypeScript team was able to build a WebAssembly interpreter in the TypeScript typesystem and then make it run DOOM, still inside the type system. Well, not quite "run", just render the first frame which took 12 days.

Anyway, it's inspiring to hear such news. Congratulations to the team!