<rss xmlns:source="http://source.scripting.com/" version="2.0">
  <channel>
    <title>re: nyman</title>
    <link>https://blog.nyman.re/</link>
    <description></description>
    
    <language>en</language>
    
    <lastBuildDate>Sun, 31 May 2026 19:26:18 +0300</lastBuildDate>
    <item>
      <title>passbolt - reviewing password managers for organisations</title>
      <link>https://blog.nyman.re/2026/05/31/passbolt-reviewing-password-managers-for.html</link>
      <pubDate>Sun, 31 May 2026 19:26:18 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2026/05/31/passbolt-reviewing-password-managers-for.html</guid>
      <description>&lt;h1 id=&#34;testing-passbolt&#34;&gt;Testing Passbolt&lt;/h1&gt;
&lt;p&gt;This is the first article in a longer series on testing password managers for organisations.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m looking for a new password manager for a smaller team. While I will start focusing on open source and EU based alternatives neither are strong requirements.&lt;/p&gt;
&lt;h2 id=&#34;conclusions&#34;&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;While it looked promising from the start, backend by a well funded company that has been around for 10+ years I ran in to so many problems that I stopped trying.&lt;/p&gt;
&lt;p&gt;The main issue was not lack of features (I never got to that part) but rather that the UX was really bad and I ran into issues which prevented me from completing my testing.&lt;/p&gt;
&lt;p&gt;Below I have documented my attempt at creating an account, logging in on another browser profile and on the iOS app.&lt;/p&gt;
&lt;p&gt;While this write-up is a bit rant-y I think the team behind passkey seem like good people and I hope they succeed. So I wanted to document the issues I ran in-to and provide suggested fixes. Hopefully someone from the team will see this.&lt;/p&gt;
&lt;h2 id=&#34;getting-started&#34;&gt;Getting started&lt;/h2&gt;
&lt;p&gt;So I signed up for a seven day trial without giving a credit card. Great. I get a sign-up email and it does not work.
&lt;img src=&#34;https://gnyman-test.micro.blog/uploads/2026/passbolt-wrong.png&#34; alt=&#34;Oops something went wrong.&#34; title=&#34;passbolt-wrong.png&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Not sure why, maybe because I tried to copy the link manually. Upon later inspection I noticed that apparently the link they provide for you to manually copy is not the same as the one behind the &amp;ldquo;Get Started!&amp;rdquo; button.&lt;/p&gt;
&lt;p&gt;No matter, I do another round of &amp;ldquo;Get Started&amp;rdquo; and this time I click the &amp;ldquo;Get Started!&amp;rdquo; button and set things up.&lt;/p&gt;
&lt;h2 id=&#34;security-token&#34;&gt;Security Token&lt;/h2&gt;
&lt;p&gt;Passbolt has a interesting idea with something they call &lt;a href=&#34;https://www.passbolt.com/docs/user/settings/browser/security-token/&#34;&gt;&amp;ldquo;Security Token&amp;rdquo;&lt;/a&gt; which is a nice additional &amp;ldquo;what you know&amp;rdquo; thing which adds a three letter secret and a colour of your choice to your password field. This is designed to make it harder for phishing to fool you as they shouldn&amp;rsquo;t know this so any fake password prompt will not have the right colour. Interesting idea, but a bit confusing.&lt;/p&gt;
&lt;p&gt;If it&amp;rsquo;s actually useful or not is another discussion, I would assume that while some might react if they are prompted to enter their passbolt login and it is the wrong colour they react, but if it&amp;rsquo;s just plain white like 99% of all other websites out there they wouldn&amp;rsquo;t think twice.&lt;/p&gt;
 &lt;img src=&#34;https://gnyman-test.micro.blog/uploads/2026/security-token.png&#34; alt=&#34;Screenshot showing the colour selection of the &amp;quot;security token&amp;quot;.&#34; title=&#34;security-token.png&#34; /&gt;
&lt;h2 id=&#34;browser-extension&#34;&gt;Browser Extension&lt;/h2&gt;
&lt;p&gt;There is no native app for Mac so I will need to use the browser extension.&lt;/p&gt;
&lt;p&gt;The browser extension install is quite slick.&lt;/p&gt;
&lt;h1 id=&#34;adding-the-extension-to-another-browserprofile&#34;&gt;Adding the extension to another browser/profile&lt;/h1&gt;
&lt;p&gt;If the initial setup was slick, this is the opposite. First you are told you need to look up the unique url from a email to be able to login. Then you are sent to a &amp;ldquo;Register&amp;rdquo;&lt;/p&gt;
&lt;img src=&#34;https://gnyman-test.micro.blog/uploads/2026/passbolt-register-again.png&#34; alt=&#34;Passbolt register again.&#34; title=&#34;passbolt-register-again.png&#34; /&gt;
&lt;p&gt;fix: allow people to enter their email and emailed a list of orgs they are linked to, this solves step 2 in the chain at the same time. There might be some confusion with self-hosted instances being on different domains but that can be worked around.&lt;/p&gt;
&lt;p&gt;This puts you into the recover account workflow.&lt;/p&gt;
&lt;img src=&#34;https://gnyman-test.micro.blog/uploads/2026/passbolt-recover-account.png&#34; alt=&#34;Passbolt recover account.&#34; title=&#34;passbolt-recover-account.png&#34; /&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; why is this account recovery? I&amp;rsquo;m not trying to to recover my account just log in on another device/browser. I understand reusing the recovery workflow but I think it&amp;rsquo;s confusing.&lt;/p&gt;
&lt;p&gt;Now you need your secret key. Here you will loose 99% of everyone except your most technical users. Most people will enter their password (the only secret they have been exposed to so far) only to be met with this even more confusing message &amp;ldquo;The key should be a valid openpgp armored key string.&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;fix:&lt;/strong&gt; I understand that we want to keep a client-secret but a full GPG key is the least user friendly choice here. Something akin to 1Passwords secret key is much better.&lt;/p&gt;
&lt;p&gt;Ok, so now I&amp;rsquo;m sufficiently technical that I can figure out what it&amp;rsquo;s looking for, go dig up the secret key, download it, and leave it in my downloads folder forever like everyone. Thus massively increasing the risk someone can steal it in the future.&lt;/p&gt;
&lt;p&gt;So I try and drag it to the webpage and of course that does not work. So I manually select it with the file-picker.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; add a droppable field&lt;/p&gt;
&lt;p&gt;After that I am prompted for my password. And then I get to pick another Security Token (what the heck? Wasn&amp;rsquo;t the idea that it would be a secret I know, is it only tied to one browser?).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; the security token is a good idea but not if it&amp;rsquo;s different on each device/profile&lt;/p&gt;
&lt;p&gt;Either way, after picking a new security token I am now logged in.&lt;/p&gt;
&lt;h2 id=&#34;mobile-app&#34;&gt;Mobile App&lt;/h2&gt;
&lt;p&gt;The mobile app setup looked much more promising compared to setting up another browser profile/desktop. It involves opening a &amp;ldquo;Mobile Setup&amp;rdquo; view in the extension which gives you a QR code to scan.&lt;/p&gt;
&lt;p&gt;That part seemed to work fine, and I am prompted to enter my password. Then I get &amp;ldquo;Server and client time is out of sync, please contact your administrator.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I have not been able to figure out what is wrong, I am using their cloud instance so I don&amp;rsquo;t think the problem is on my side.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;fix: &lt;/strong&gt; I guess fix whatever is causing this? :-)&lt;/p&gt;
&lt;h2 id=&#34;end-rant&#34;&gt;End rant&lt;/h2&gt;
&lt;p&gt;But Gabriel, Passbolt is open source, stop whining and just contribute.&lt;/p&gt;
&lt;p&gt;Yes passbolt is open source, from a &lt;a href=&#34;https://www.passbolt.com/blog/passbolt-raises-8m-series-a-led-by-airbridge&#34;&gt;well funded&lt;/a&gt; for-profit company. I want them to succeed and I&amp;rsquo;m contributing by documenting what isn&amp;rsquo;t working.&lt;/p&gt;
&lt;p&gt;The passbolt people seem to know their stuff security wise, but security must be easy to use.&lt;/p&gt;
</description>
      <source:markdown># Testing Passbolt

This is the first article in a longer series on testing password managers for organisations.

I&#39;m looking for a new password manager for a smaller team. While I will start focusing on open source and EU based alternatives neither are strong requirements.

## Conclusions
While it looked promising from the start, backend by a well funded company that has been around for 10+ years I ran in to so many problems that I stopped trying.

The main issue was not lack of features (I never got to that part) but rather that the UX was really bad and I ran into issues which prevented me from completing my testing.

Below I have documented my attempt at creating an account, logging in on another browser profile and on the iOS app.

While this write-up is a bit rant-y I think the team behind passkey seem like good people and I hope they succeed. So I wanted to document the issues I ran in-to and provide suggested fixes. Hopefully someone from the team will see this.

## Getting started
So I signed up for a seven day trial without giving a credit card. Great. I get a sign-up email and it does not work.
&lt;img src=&#34;https://gnyman-test.micro.blog/uploads/2026/passbolt-wrong.png&#34; alt=&#34;Oops something went wrong.&#34; title=&#34;passbolt-wrong.png&#34; /&gt;

Not sure why, maybe because I tried to copy the link manually. Upon later inspection I noticed that apparently the link they provide for you to manually copy is not the same as the one behind the &#34;Get Started!&#34; button.

No matter, I do another round of &#34;Get Started&#34; and this time I click the &#34;Get Started!&#34; button and set things up.

## Security Token
Passbolt has a interesting idea with something they call [&#34;Security Token&#34;](https://www.passbolt.com/docs/user/settings/browser/security-token/) which is a nice additional &#34;what you know&#34; thing which adds a three letter secret and a colour of your choice to your password field. This is designed to make it harder for phishing to fool you as they shouldn&#39;t know this so any fake password prompt will not have the right colour. Interesting idea, but a bit confusing.

If it&#39;s actually useful or not is another discussion, I would assume that while some might react if they are prompted to enter their passbolt login and it is the wrong colour they react, but if it&#39;s just plain white like 99% of all other websites out there they wouldn&#39;t think twice.

 &lt;img src=&#34;https://gnyman-test.micro.blog/uploads/2026/security-token.png&#34; alt=&#34;Screenshot showing the colour selection of the &amp;quot;security token&amp;quot;.&#34; title=&#34;security-token.png&#34; /&gt;

## Browser Extension
There is no native app for Mac so I will need to use the browser extension.

The browser extension install is quite slick.

# Adding the extension to another browser/profile
If the initial setup was slick, this is the opposite. First you are told you need to look up the unique url from a email to be able to login. Then you are sent to a &#34;Register&#34;

&lt;img src=&#34;https://gnyman-test.micro.blog/uploads/2026/passbolt-register-again.png&#34; alt=&#34;Passbolt register again.&#34; title=&#34;passbolt-register-again.png&#34; /&gt;

fix: allow people to enter their email and emailed a list of orgs they are linked to, this solves step 2 in the chain at the same time. There might be some confusion with self-hosted instances being on different domains but that can be worked around.

This puts you into the recover account workflow.

&lt;img src=&#34;https://gnyman-test.micro.blog/uploads/2026/passbolt-recover-account.png&#34; alt=&#34;Passbolt recover account.&#34; title=&#34;passbolt-recover-account.png&#34; /&gt;

&lt;strong&gt;Fix:&lt;/strong&gt; why is this account recovery? I&#39;m not trying to to recover my account just log in on another device/browser. I understand reusing the recovery workflow but I think it&#39;s confusing.


Now you need your secret key. Here you will loose 99% of everyone except your most technical users. Most people will enter their password (the only secret they have been exposed to so far) only to be met with this even more confusing message &#34;The key should be a valid openpgp armored key string.&#34;.

**fix:** I understand that we want to keep a client-secret but a full GPG key is the least user friendly choice here. Something akin to 1Passwords secret key is much better.

Ok, so now I&#39;m sufficiently technical that I can figure out what it&#39;s looking for, go dig up the secret key, download it, and leave it in my downloads folder forever like everyone. Thus massively increasing the risk someone can steal it in the future.

So I try and drag it to the webpage and of course that does not work. So I manually select it with the file-picker.

&lt;strong&gt;Fix:&lt;/strong&gt; add a droppable field

After that I am prompted for my password. And then I get to pick another Security Token (what the heck? Wasn&#39;t the idea that it would be a secret I know, is it only tied to one browser?).

&lt;strong&gt;Fix:&lt;/strong&gt; the security token is a good idea but not if it&#39;s different on each device/profile

Either way, after picking a new security token I am now logged in.

## Mobile App

The mobile app setup looked much more promising compared to setting up another browser profile/desktop. It involves opening a &#34;Mobile Setup&#34; view in the extension which gives you a QR code to scan.

That part seemed to work fine, and I am prompted to enter my password. Then I get &#34;Server and client time is out of sync, please contact your administrator.&#34;

I have not been able to figure out what is wrong, I am using their cloud instance so I don&#39;t think the problem is on my side.

&lt;strong&gt;fix: &lt;/strong&gt; I guess fix whatever is causing this? :-)

## End rant

But Gabriel, Passbolt is open source, stop whining and just contribute.

Yes passbolt is open source, from a [well funded](https://www.passbolt.com/blog/passbolt-raises-8m-series-a-led-by-airbridge) for-profit company. I want them to succeed and I&#39;m contributing by documenting what isn&#39;t working.

The passbolt people seem to know their stuff security wise, but security must be easy to use.
</source:markdown>
    </item>
    
    <item>
      <title>Magic links are not great but they are the right choice sometimes</title>
      <link>https://blog.nyman.re/2025/09/24/magic-links-are-not-great.html</link>
      <pubDate>Wed, 24 Sep 2025 10:47:50 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/09/24/magic-links-are-not-great.html</guid>
      <description>&lt;p&gt;LLM disclaimer: this post was written based on a discussion with claude and drafted by claude. I have edited it heavily but if you&amp;rsquo;re allergic to LLM&amp;rsquo;s feel free to skip it.&lt;/p&gt;
&lt;p&gt;The setup: we&amp;rsquo;re have a policy compliance system that employees use roughly once per year to check boxes confirming they&amp;rsquo;ve read our updated policies. The right solution is to integrate this with our existing SSO solution that has proper anomaly detection, brute force protection, and phishing-resistant auth and so on. But for various organizational reasons (aren&amp;rsquo;t there always), that integration will take longer than I&amp;rsquo;d like.&lt;/p&gt;
&lt;p&gt;So I started thinking - could we build our own lightweight OIDC service? Something that just validates &amp;ldquo;yes, this person controls this email address&amp;rdquo; and nothing more. I&amp;rsquo;m usually not a fan of magic-links, but could they work in this case.&lt;/p&gt;
&lt;p&gt;To do some rubber ducking, I asked claude. Claude&amp;rsquo;s initial reaction was something akin to telling me thats the stupidest idea ever (because I told it to argue with me, not debate and it dutifully complied). It made upp all kinds of stuff like telling me it was outsourcing authentication to email, a fundamentally insecure transport designed in 1982. Every magic link gets cached in browser history, server logs, forwarded emails, screenshots - it&amp;rsquo;s a mess.&lt;/p&gt;
&lt;p&gt;But I pushed back. The tokens are time based and ephemeral. The alternative is to have yet another password, making people pick bad passwords or just click &amp;ldquo;forgot password&amp;rdquo; anyway, which&amp;hellip; is just a magic link with extra steps. Magic links are not changing the security model, we&amp;rsquo;re just making it honest about where the security bundary is.&lt;/p&gt;
&lt;p&gt;It also had arguments about how it trains people to click links in emails, as if they would training in that. We have told people not to do that for 20 years and it hasn&amp;rsquo;t done much good. You just can&amp;rsquo;t have that as a security boundary.&lt;/p&gt;
&lt;p&gt;Claude eventually conceded this point (of course it did, it&amp;rsquo;s designed to please and not fight) but it did raise one thing. Social engineering. If you implement the magic link in a way clicking the link logs you in where you requested it, that lets attackers call pretending to be IT, say &amp;ldquo;we just sent you a verification link, can you click it?&amp;rdquo; and boom, they&amp;rsquo;re in.&lt;/p&gt;
&lt;p&gt;The fix is device binding (with a cookie) - when someone requests a magic link, you set a random cookie. The magic link only works if that cookie is present. If they click from a different device or browser, you explain what happened and let them either open it in the original browser or request a new link (invalidating the old one).&lt;/p&gt;
&lt;p&gt;But when I considered this, I realised this is the wrong solution. Not because it does not work, but because it introduces a bigger risk. Strong device binding means if I normally check email on my phone but I&amp;rsquo;m now trying to access the policy system on my home laptop, I&amp;rsquo;m now forced to log into webmail on my home computer. If that device is compromised, I now have a full email comprimise instead of giving an attacker access to a compliance system. The security measure backfired by pushing risk to a more valuable target.&lt;/p&gt;
&lt;p&gt;The alternative is OTP codes - send a 6-digit number to email, user types it in. This solves the device forcing problem but it&amp;rsquo;s vulnerable to social engineering. &amp;ldquo;Hi, this is IT, we need that code to complete your compliance verification.&amp;rdquo; Most people are wired to trust humans over machines, and are likely to comply even when the email explicitly says &amp;ldquo;DO NOT SHARE THIS CODE.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;(Banks have been fighting this battle for years. The code comes with a  big warning saying &amp;ldquo;DO NO SHARE&amp;rdquo;, but when the scammer says &amp;ldquo;ignore that, this is a special case,&amp;rdquo; many will comply. )&lt;/p&gt;
&lt;p&gt;So what&amp;rsquo;s the right choice? First, let&amp;rsquo;s be honest: the right choice is to use your existing IdP with proper security infrastructure. If you can&amp;rsquo;t do that, then it depends on your threat model and what you&amp;rsquo;re protecting.&lt;/p&gt;
&lt;p&gt;For a genuinely low-value system like policy compliance, OTP phishing gets the attacker access to boring checkboxes. Device binding, while stopping the social-engineering angle, introduces the risk of a full email compromise. The math is clear - accept the smaller vulnerability to avoid creating bigger ones.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a running joke that the only answer you get from security people when you ask about something is &amp;ldquo;it depends&amp;rdquo; but that&amp;rsquo;s because it&amp;rsquo;s true.&lt;/p&gt;
&lt;p&gt;Pragmatic security means working within organizational constraints and accepting smaller vulnerabilities to avoid creating bigger ones.&lt;/p&gt;
</description>
      <source:markdown>LLM disclaimer: this post was written based on a discussion with claude and drafted by claude. I have edited it heavily but if you&#39;re allergic to LLM&#39;s feel free to skip it.

The setup: we&#39;re have a policy compliance system that employees use roughly once per year to check boxes confirming they&#39;ve read our updated policies. The right solution is to integrate this with our existing SSO solution that has proper anomaly detection, brute force protection, and phishing-resistant auth and so on. But for various organizational reasons (aren&#39;t there always), that integration will take longer than I&#39;d like.

So I started thinking - could we build our own lightweight OIDC service? Something that just validates &#34;yes, this person controls this email address&#34; and nothing more. I&#39;m usually not a fan of magic-links, but could they work in this case.

To do some rubber ducking, I asked claude. Claude&#39;s initial reaction was something akin to telling me thats the stupidest idea ever (because I told it to argue with me, not debate and it dutifully complied). It made upp all kinds of stuff like telling me it was outsourcing authentication to email, a fundamentally insecure transport designed in 1982. Every magic link gets cached in browser history, server logs, forwarded emails, screenshots - it&#39;s a mess. 

But I pushed back. The tokens are time based and ephemeral. The alternative is to have yet another password, making people pick bad passwords or just click &#34;forgot password&#34; anyway, which... is just a magic link with extra steps. Magic links are not changing the security model, we&#39;re just making it honest about where the security bundary is.

It also had arguments about how it trains people to click links in emails, as if they would training in that. We have told people not to do that for 20 years and it hasn&#39;t done much good. You just can&#39;t have that as a security boundary.

Claude eventually conceded this point (of course it did, it&#39;s designed to please and not fight) but it did raise one thing. Social engineering. If you implement the magic link in a way clicking the link logs you in where you requested it, that lets attackers call pretending to be IT, say &#34;we just sent you a verification link, can you click it?&#34; and boom, they&#39;re in.

The fix is device binding (with a cookie) - when someone requests a magic link, you set a random cookie. The magic link only works if that cookie is present. If they click from a different device or browser, you explain what happened and let them either open it in the original browser or request a new link (invalidating the old one).

But when I considered this, I realised this is the wrong solution. Not because it does not work, but because it introduces a bigger risk. Strong device binding means if I normally check email on my phone but I&#39;m now trying to access the policy system on my home laptop, I&#39;m now forced to log into webmail on my home computer. If that device is compromised, I now have a full email comprimise instead of giving an attacker access to a compliance system. The security measure backfired by pushing risk to a more valuable target.

The alternative is OTP codes - send a 6-digit number to email, user types it in. This solves the device forcing problem but it&#39;s vulnerable to social engineering. &#34;Hi, this is IT, we need that code to complete your compliance verification.&#34; Most people are wired to trust humans over machines, and are likely to comply even when the email explicitly says &#34;DO NOT SHARE THIS CODE.&#34;

(Banks have been fighting this battle for years. The code comes with a  big warning saying &#34;DO NO SHARE&#34;, but when the scammer says &#34;ignore that, this is a special case,&#34; many will comply. )

So what&#39;s the right choice? First, let&#39;s be honest: the right choice is to use your existing IdP with proper security infrastructure. If you can&#39;t do that, then it depends on your threat model and what you&#39;re protecting.

For a genuinely low-value system like policy compliance, OTP phishing gets the attacker access to boring checkboxes. Device binding, while stopping the social-engineering angle, introduces the risk of a full email compromise. The math is clear - accept the smaller vulnerability to avoid creating bigger ones.

It&#39;s a running joke that the only answer you get from security people when you ask about something is &#34;it depends&#34; but that&#39;s because it&#39;s true.

Pragmatic security means working within organizational constraints and accepting smaller vulnerabilities to avoid creating bigger ones.
</source:markdown>
    </item>
    
    <item>
      <title>Thank you for blaugust</title>
      <link>https://blog.nyman.re/2025/08/31/thank-you-for-blaugust.html</link>
      <pubDate>Sun, 31 Aug 2025 22:05:06 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/31/thank-you-for-blaugust.html</guid>
      <description>&lt;p&gt;This is the real end-of-blaugust post.&lt;/p&gt;
&lt;p&gt;I really enjoyed taking part, even if my partaking was very isolated.&lt;/p&gt;
&lt;p&gt;Now towards the end, I decided to check &lt;a href=&#34;https://nerdgirlthoughts.game.blog/category/blaugust-2025/&#34;&gt;blog of this years blaugust host&lt;/a&gt; and&amp;hellip; why the heck did I not do that earlier.&lt;/p&gt;
&lt;p&gt;Instead of trying to force out some micro blogs, I could taken inspiration from the massive amount of material available there.&lt;/p&gt;
&lt;p&gt;As none of my posts are ready to be posted, I&amp;rsquo;ll take the opportunity to wrap it up with the suggested &amp;ldquo;Lessons Learned&amp;rdquo; topics from the &lt;a href=&#34;https://nerdgirlthoughts.game.blog/2025/07/25/blaugust-2025-calendar-weekly-prompts/&#34;&gt;blaugust calendar&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;if-you-could-go-back-in-time-to-august-1st-or-whenever-you-signed-up-for-blaugust-what-would-you-do-differently-to-prepare&#34;&gt;If you could go back in time to August 1st (or whenever you signed up for Blaugust), what would you do differently to prepare?&lt;/h3&gt;
&lt;p&gt;I would join the discord, and add the blog of the blaugust host to my RSS feed. Feels kind of dumb that I did not do that earlier.&lt;/p&gt;
&lt;h3 id=&#34;what-are-your-top-3-or-5-takeaways-from-your-blaugust-experience&#34;&gt;What are your top 3 or 5 takeaways from your Blaugust experience?&lt;/h3&gt;
&lt;p&gt;I realised once again that I don&amp;rsquo;t get things done without deadlines (does anyone?). But also that when I put a bit of effort onto it, I can do it.&lt;/p&gt;
&lt;p&gt;The other thing is that although I took the opportunity to publish a bunch of my drafts and other thoughts, I had not&lt;/p&gt;
&lt;h3 id=&#34;were-there-any-posts-your-read-during-blaugust-that-changed-how-you-felt-about-the-experience-as-a-whole&#34;&gt;Were there any posts your read during Blaugust that changed how you felt about the experience as a whole?&lt;/h3&gt;
&lt;p&gt;Not really, because I have not read any except a few posts from people I already followed before who happened to take part.&lt;/p&gt;
&lt;h3 id=&#34;what-were-your-favorite-and-least-favorite-parts-of-participating-in-blaugust-2025&#34;&gt;What were your favorite and least favorite parts of participating in Blaugust 2025?&lt;/h3&gt;
&lt;p&gt;Favourite part was the whole concept and idea. It&amp;rsquo;s kind of nice that it&amp;rsquo;s small, because that meant I felt that it was small enough that I would not get lost in the noise.&lt;/p&gt;
&lt;p&gt;The least favourite. The first thing I think of is a bit dumb, but that would be the lack of spam.&lt;/p&gt;
&lt;p&gt;Yeah you heard me right. Lack of spam.&lt;/p&gt;
&lt;p&gt;Maybe I missed to check a box, or maybe the information channels were somewhere else. But I don&amp;rsquo;t think I&amp;rsquo;ve gotten any kind of email about my partaking in Blaugust.&lt;/p&gt;
&lt;p&gt;Maybe if I&amp;rsquo;d joined discord I would have gotten the notifications, but I have never gotten into the habit of using discord.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not saying this is something that someone &amp;ldquo;should&amp;rdquo; have done, I understand this is a volunteer driven thing so I don&amp;rsquo;t have any expectations. Just something I realised now at the end.&lt;/p&gt;
&lt;h2 id=&#34;to-conclude&#34;&gt;To conclude&lt;/h2&gt;
&lt;p&gt;I will definitely take part in Blaugust next year. And in a more active role. I just joined the discord and I&amp;rsquo;ll keep my eyes out for some other posting &amp;ldquo;challenge&amp;rdquo; in order to ensure I keep the words flowing.&lt;/p&gt;
&lt;p&gt;Writing is such an important part of our daily communication nowadays, and practice makes perfect.&lt;/p&gt;
&lt;p&gt;Thank you for the fish.&lt;/p&gt;
</description>
      <source:markdown>This is the real end-of-blaugust post.

I really enjoyed taking part, even if my partaking was very isolated.

Now towards the end, I decided to check [blog of this years blaugust host](https://nerdgirlthoughts.game.blog/category/blaugust-2025/) and... why the heck did I not do that earlier.

Instead of trying to force out some micro blogs, I could taken inspiration from the massive amount of material available there.

As none of my posts are ready to be posted, I&#39;ll take the opportunity to wrap it up with the suggested &#34;Lessons Learned&#34; topics from the [blaugust calendar](https://nerdgirlthoughts.game.blog/2025/07/25/blaugust-2025-calendar-weekly-prompts/).

### If you could go back in time to August 1st (or whenever you signed up for Blaugust), what would you do differently to prepare?

I would join the discord, and add the blog of the blaugust host to my RSS feed. Feels kind of dumb that I did not do that earlier.

### What are your top 3 or 5 takeaways from your Blaugust experience?

I realised once again that I don&#39;t get things done without deadlines (does anyone?). But also that when I put a bit of effort onto it, I can do it.

The other thing is that although I took the opportunity to publish a bunch of my drafts and other thoughts, I had not 


### Were there any posts your read during Blaugust that changed how you felt about the experience as a whole?

Not really, because I have not read any except a few posts from people I already followed before who happened to take part.

### What were your favorite and least favorite parts of participating in Blaugust 2025?

Favourite part was the whole concept and idea. It&#39;s kind of nice that it&#39;s small, because that meant I felt that it was small enough that I would not get lost in the noise.

The least favourite. The first thing I think of is a bit dumb, but that would be the lack of spam. 

Yeah you heard me right. Lack of spam.

Maybe I missed to check a box, or maybe the information channels were somewhere else. But I don&#39;t think I&#39;ve gotten any kind of email about my partaking in Blaugust.

Maybe if I&#39;d joined discord I would have gotten the notifications, but I have never gotten into the habit of using discord.

I&#39;m not saying this is something that someone &#34;should&#34; have done, I understand this is a volunteer driven thing so I don&#39;t have any expectations. Just something I realised now at the end.



## To conclude

I will definitely take part in Blaugust next year. And in a more active role. I just joined the discord and I&#39;ll keep my eyes out for some other posting &#34;challenge&#34; in order to ensure I keep the words flowing.

Writing is such an important part of our daily communication nowadays, and practice makes perfect.

Thank you for the fish.
</source:markdown>
    </item>
    
    <item>
      <title>Blaugust near the end thoughts</title>
      <link>https://blog.nyman.re/2025/08/30/blaugust-near-the-end-thoughts.html</link>
      <pubDate>Sat, 30 Aug 2025 23:21:15 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/30/blaugust-near-the-end-thoughts.html</guid>
      <description>&lt;p&gt;Second to last day&lt;/p&gt;
&lt;p&gt;This was going to be the launch of the new version of hasmypasswordbeenstolen.net, but my high standard got to me. It will be out soon-ish but not yet.&lt;/p&gt;
&lt;p&gt;As for blaugust, it has been great. Although one blog per day is clearly too much for me. It has still been a great encouragement to get a few old ones out, and some new ones also.&lt;/p&gt;
&lt;p&gt;I will try to keep up the posting, but without a specific deadline to hit (midnight) there is a high risk that I just go back to perfecting things forever.&lt;/p&gt;
&lt;p&gt;Hopefully not.&lt;/p&gt;
</description>
      <source:markdown>Second to last day

This was going to be the launch of the new version of hasmypasswordbeenstolen.net, but my high standard got to me. It will be out soon-ish but not yet.

As for blaugust, it has been great. Although one blog per day is clearly too much for me. It has still been a great encouragement to get a few old ones out, and some new ones also.

I will try to keep up the posting, but without a specific deadline to hit (midnight) there is a high risk that I just go back to perfecting things forever.

Hopefully not.
</source:markdown>
    </item>
    
    <item>
      <title>More mailinator thoughts</title>
      <link>https://blog.nyman.re/2025/08/29/more-mailinator-thoughts.html</link>
      <pubDate>Fri, 29 Aug 2025 23:13:22 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/29/more-mailinator-thoughts.html</guid>
      <description>&lt;p&gt;Continuing the trend of praising mailinator, turns out one of the reasons feature creep did not get to it, was that it was or maybe is still &amp;ldquo;just&amp;rdquo; a side project, this interview with the creator has a bunch of interesting details.&lt;/p&gt;
&lt;p&gt;The interview is a few years old by now, but still interesting.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://nathanlatkathetop.libsyn.com/703-the-accidental-10kmo-side-project&#34;&gt;nathanlatkathetop.libsyn.com/703-the-a&amp;hellip;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Also this presentation from a few years later is also interesting, also old. Things have probably changed since then.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://paul.mailinator.com/presentation-the-technical-evolution-of-mailinator-com/&#34;&gt;paul.mailinator.com/presentat&amp;hellip;&lt;/a&gt;&lt;/p&gt;
</description>
      <source:markdown>Continuing the trend of praising mailinator, turns out one of the reasons feature creep did not get to it, was that it was or maybe is still &#34;just&#34; a side project, this interview with the creator has a bunch of interesting details.

The interview is a few years old by now, but still interesting.

[nathanlatkathetop.libsyn.com/703-the-a...](https://nathanlatkathetop.libsyn.com/703-the-accidental-10kmo-side-project)

Also this presentation from a few years later is also interesting, also old. Things have probably changed since then.

[paul.mailinator.com/presentat...](https://paul.mailinator.com/presentation-the-technical-evolution-of-mailinator-com/)

</source:markdown>
    </item>
    
    <item>
      <title>Setting up your own mailinator domain</title>
      <link>https://blog.nyman.re/2025/08/28/setting-up-your-own-mailinator.html</link>
      <pubDate>Thu, 28 Aug 2025 23:26:09 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/28/setting-up-your-own-mailinator.html</guid>
      <description>&lt;p&gt;Mailinator is one of those great forever services which I seriously hope will never disappear, it has saved me from so much &amp;ldquo;newsletters&amp;rdquo; and other things I don&amp;rsquo;t want in my mailbox.&lt;/p&gt;
&lt;p&gt;Sadly it for some reason people think it&amp;rsquo;s a good idea to block it sometimes.&lt;/p&gt;
&lt;p&gt;Which makes absolutely no sense to me. I mean, I am clearly indicating that I &lt;strong&gt;do not&lt;/strong&gt; want email from you. If you force me to give my own email, and send me emails (which we know you will) I will put them into spam.&lt;/p&gt;
&lt;p&gt;I am quite certain Google uses that as a signal to start feeding your newsletter into spam for the users who might actually want it.&lt;/p&gt;
&lt;p&gt;Either way, that aside.&lt;/p&gt;
&lt;h2 id=&#34;your-own-mailinator-domain&#34;&gt;Your own mailinator domain&lt;/h2&gt;
&lt;p&gt;This seems to work with most services which tries to prevent mailinator.com and the other known mailinator domains from being used.&lt;/p&gt;
&lt;p&gt;You will need your own domain and be somewhat comfortable with editing the DNS of it for this to work. If you are, there is no risk with doing this. If you do not have your own domain, you can get a free subdomain at afraid.org.&lt;/p&gt;
&lt;p&gt;You can do this two different ways, either create a domain which is a &lt;code&gt;CNAME&lt;/code&gt; for &lt;code&gt;mail.mailinator.com&lt;/code&gt;, this is more easily detected if they are motivated.&lt;/p&gt;
&lt;p&gt;The other way is to do the following&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;create a &lt;code&gt;A&lt;/code&gt; record pointing to the same destination as &lt;code&gt;mail.mailinator.com&lt;/code&gt; (you have to look it up, it ends at &lt;code&gt;.11.30&lt;/code&gt;) , for example &lt;code&gt;nospam.your.domain&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;then create a &lt;code&gt;MX&lt;/code&gt; record like &lt;code&gt;nospam.your.domain 10:nospam.your.domain.&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Success!&lt;/p&gt;
&lt;p&gt;Mailinator does not seem to mind doing this, at least in 2008 &lt;a href=&#34;https://mailinator.blogspot.com/2008/01/your-own-private-mailinator.html&#34;&gt;https://mailinator.blogspot.com/2008/01/your-own-private-mailinator.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Using the A method hides it, but sometimes it still does not work. I assume some people have blacklisted the &lt;code&gt;[redacted].11.30&lt;/code&gt; IP which mailinator uses as MX. That IP has been the same for as long as I&amp;rsquo;ve used them and probably longer.&lt;/p&gt;
&lt;p&gt;Enjoy.&lt;/p&gt;
</description>
      <source:markdown>Mailinator is one of those great forever services which I seriously hope will never disappear, it has saved me from so much &#34;newsletters&#34; and other things I don&#39;t want in my mailbox.

Sadly it for some reason people think it&#39;s a good idea to block it sometimes.

Which makes absolutely no sense to me. I mean, I am clearly indicating that I &lt;strong&gt;do not&lt;/strong&gt; want email from you. If you force me to give my own email, and send me emails (which we know you will) I will put them into spam.

I am quite certain Google uses that as a signal to start feeding your newsletter into spam for the users who might actually want it.

Either way, that aside.

## Your own mailinator domain

This seems to work with most services which tries to prevent mailinator.com and the other known mailinator domains from being used.

You will need your own domain and be somewhat comfortable with editing the DNS of it for this to work. If you are, there is no risk with doing this. If you do not have your own domain, you can get a free subdomain at afraid.org.

You can do this two different ways, either create a domain which is a `CNAME` for `mail.mailinator.com`, this is more easily detected if they are motivated.

The other way is to do the following

1. create a `A` record pointing to the same destination as `mail.mailinator.com` (you have to look it up, it ends at `.11.30`) , for example `nospam.your.domain`
2. then create a `MX` record like `nospam.your.domain 10:nospam.your.domain.`

Success!

Mailinator does not seem to mind doing this, at least in 2008 https://mailinator.blogspot.com/2008/01/your-own-private-mailinator.html

Using the A method hides it, but sometimes it still does not work. I assume some people have blacklisted the `[redacted].11.30` IP which mailinator uses as MX. That IP has been the same for as long as I&#39;ve used them and probably longer.

Enjoy.
</source:markdown>
    </item>
    
    <item>
      <title>Reviewing the charities annual reports</title>
      <link>https://blog.nyman.re/2025/08/27/reviewing-the-charities-annual-reports.html</link>
      <pubDate>Wed, 27 Aug 2025 23:18:53 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/27/reviewing-the-charities-annual-reports.html</guid>
      <description>&lt;p&gt;This is a follow up to [this](&lt;a href=&#34;https://blog.nyman.re/2025/08/23/what-can-you-do.html&#34;&gt;blog.nyman.re/2025/08/2&amp;hellip;&lt;/a&gt; and &lt;a href=&#34;https://blog.nyman.re/2025/08/24/finding-a-charity-where-you.html&#34;&gt;this&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So we&amp;rsquo;re continuing the quest to figure if it&amp;rsquo;s realistic for a normal person in the tech industry to do a life-saving donation.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look closer at the charities listed last time by reading their yearly reports.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://medeor.de/images/dokumente/2024-Jahresbericht-action-medeor-EN.pdf&#34;&gt;Medeor&amp;rsquo;s report for 2024&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Overall a very good report. Very transparent. From this we can read that they have received, around 34 million in funding. They have a lot of different projects ongoing in different countries.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://impact.kidsor.org/impactreport2024/&#34;&gt;KidsOR Impact report&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is not the same as the meteor one, it&amp;rsquo;s a flashy html page. It does not have any numbers, but those are available in the &lt;a href=&#34;https://www.kidsor.org/assets/uploads/files/downloads/Kids%20Operating%20Room%2031%20December%202024%20Signed%20AAB%20Audit%20LLP-compressed.pdf&#34;&gt;annual financial report&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Their yearly donations are GBP 5,6M in 2023 and 10,6M in 2024. They used 7M for charitable activities and 400K for fundraising.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.chainofhope.org/fileadmin/uploads/coh/Documents/COH-ANNUAL-REVIEW-FINAL-23-10-24.pdf&#34;&gt;Chain of Hope&amp;rsquo;s Annual Review&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Annual donations in the range GBP 4,4 M 2023 to 3,4M in 2024. Notable there is that the document says 6% of the money went to support costs, and 16% to cost of generating funds. Without diving deeper into this, it looks higher than the others but it might just be that they are more transparent.&lt;/p&gt;
&lt;p&gt;I think based on this, I&amp;rsquo;m leaning towards KidsOR. And as next step I&amp;rsquo;ll probably try to figure out if my donation would be big enough that it could be &amp;ldquo;easily&amp;rdquo; earmarked so I can know what difference it made.&lt;/p&gt;
&lt;p&gt;Medeor says that anything above 1000 euro is considered a large donation, big enough for the donor to contact them beforehand to discuss.&lt;/p&gt;
&lt;p&gt;I could not find any info about that on KidsOR.&lt;/p&gt;
</description>
      <source:markdown>This is a follow up to [this]([blog.nyman.re/2025/08/2...](https://blog.nyman.re/2025/08/23/what-can-you-do.html) and [this](https://blog.nyman.re/2025/08/24/finding-a-charity-where-you.html).

So we&#39;re continuing the quest to figure if it&#39;s realistic for a normal person in the tech industry to do a life-saving donation.

Let&#39;s look closer at the charities listed last time by reading their yearly reports.

[Medeor&#39;s report for 2024](https://medeor.de/images/dokumente/2024-Jahresbericht-action-medeor-EN.pdf)

Overall a very good report. Very transparent. From this we can read that they have received, around 34 million in funding. They have a lot of different projects ongoing in different countries.

[KidsOR Impact report](https://impact.kidsor.org/impactreport2024/)

This is not the same as the meteor one, it&#39;s a flashy html page. It does not have any numbers, but those are available in the [annual financial report](https://www.kidsor.org/assets/uploads/files/downloads/Kids%20Operating%20Room%2031%20December%202024%20Signed%20AAB%20Audit%20LLP-compressed.pdf).

Their yearly donations are GBP 5,6M in 2023 and 10,6M in 2024. They used 7M for charitable activities and 400K for fundraising. 

[Chain of Hope&#39;s Annual Review](https://www.chainofhope.org/fileadmin/uploads/coh/Documents/COH-ANNUAL-REVIEW-FINAL-23-10-24.pdf)

Annual donations in the range GBP 4,4 M 2023 to 3,4M in 2024. Notable there is that the document says 6% of the money went to support costs, and 16% to cost of generating funds. Without diving deeper into this, it looks higher than the others but it might just be that they are more transparent.


I think based on this, I&#39;m leaning towards KidsOR. And as next step I&#39;ll probably try to figure out if my donation would be big enough that it could be &#34;easily&#34; earmarked so I can know what difference it made.

Medeor says that anything above 1000 euro is considered a large donation, big enough for the donor to contact them beforehand to discuss.

I could not find any info about that on KidsOR.
</source:markdown>
    </item>
    
    <item>
      <title>Dishonest game developers</title>
      <link>https://blog.nyman.re/2025/08/26/dishonest-game-developers.html</link>
      <pubDate>Tue, 26 Aug 2025 23:23:09 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/26/dishonest-game-developers.html</guid>
      <description>&lt;p&gt;&lt;em&gt;Note&lt;/em&gt; This is a very old blog draft, all the way from 2019 when you could still download Fortnite on your iPad (which you can again but it&amp;rsquo;s a bit harder and you need to live in EU). But the point still stands I need to publish something. Enjoy, or not :-)&lt;/p&gt;
&lt;p&gt;I recently tried Fortnite for the first time, thinking I would check out what the fuzz was about.
So I install it on the iPad and get started. Knowing roughly how to play it, I can&amp;rsquo;t even find a weapon in the first round and get hosed by obvious AI players (henchmen). No worries, I try again&amp;hellip; and I  go on to beat the crap out everyone the two next rounds,  Victory Royale, numero uno, #1. Damn I&amp;rsquo;m good.. or am I?&lt;/p&gt;
&lt;p&gt;I do think I&amp;rsquo;m above the average gamer but this seems a bit suspicious.  Maybe they have some matchmaking and my hideous performance in the first match put me in the lowest bracket?&lt;/p&gt;
&lt;p&gt;Or&amp;hellip; Maybe the other players were not players but really bad bots? A quick googling confirms that indeed, Epic added bots last autumn (editors note, autumn of 2019).&lt;/p&gt;
&lt;p&gt;And there does not seem to be any obvious way to identify them, except that people seem to agree that they are quite bad.&lt;/p&gt;
&lt;p&gt;So turns out I am not really good at this game?&amp;hellip; &lt;strong&gt;sad trombone&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I spent some time thinking about this, and I read &lt;a href=&#34;https://www.polygon.com/2019/10/22/20926888/fortnite-bots-skill-based-matchmaking-epic-games&#34;&gt;an article&lt;/a&gt; discussing it. Turns out not everybody was happy with this silent introduction of bots. Making people think they are better than they are to make them enjoy the game more does seem, wrong on some level.&lt;/p&gt;
&lt;p&gt;Or is it? What is the goal from playing games? I guess most people are not looking to become full time pro-gamers but rather just want to have some fun.&lt;/p&gt;
&lt;p&gt;Is it wrong to let them enjoy it? Not everyone enjoys loosing. I recall playing StarCraft 2, where the matchmaking was good and made sure you lost roughly half the time by matching you against better players every time you improved. And that was not always fun. Because when you&amp;rsquo;re perfectly matched it might well be that you play five games in a row and loose all of them. It was one of the reasons I did not play ladder SC2 but just fooled around in coop and other stuff.&lt;/p&gt;
&lt;p&gt;But if we accept that developers cheat a bit to allow us to enjoy the game more. What is &amp;ldquo;a bit&amp;rdquo;. I am not sure there are any easy answers, it all depends.&lt;/p&gt;
&lt;p&gt;How hard do we want our games to be? I guess the most fun part if you believe you win by a close margin.&lt;/p&gt;
&lt;p&gt;And then again, Fortnite was not the first game to do this. Mariokart might have been one of the earlier ones to do it. It really skews the game to give worse players a chance. When you&amp;rsquo;re in the lead or close to it, you will just pick up bad items. While if you&amp;rsquo;re at the back you get all the good suff. And in single player the computer automatically adapted to your speed.&lt;/p&gt;
&lt;p&gt;Asphalt 9 (at least on iPad) is also a obvious example of this, try playing a single player map with a massively over-powered car (compared to what is the recommended level on that course). Now watch how every computer keeps up/stays ahead until ~70% of the course is done. Then falls behind massively on the last 20% to make sure you win if you &amp;ldquo;should&amp;rdquo; win (based on the points in your car).&lt;/p&gt;
&lt;p&gt;To conclude, I&amp;rsquo;ll just say I don&amp;rsquo;t know what is the right way. If the developers made it too obvious that they are letting you win, most people would not think that to be fun. But on the other hand, people like winning more than loosing.&lt;/p&gt;
</description>
      <source:markdown>*Note* This is a very old blog draft, all the way from 2019 when you could still download Fortnite on your iPad (which you can again but it&#39;s a bit harder and you need to live in EU). But the point still stands I need to publish something. Enjoy, or not :-)

I recently tried Fortnite for the first time, thinking I would check out what the fuzz was about. 
So I install it on the iPad and get started. Knowing roughly how to play it, I can&#39;t even find a weapon in the first round and get hosed by obvious AI players (henchmen). No worries, I try again... and I  go on to beat the crap out everyone the two next rounds,  Victory Royale, numero uno, #1. Damn I&#39;m good.. or am I?  

I do think I&#39;m above the average gamer but this seems a bit suspicious.  Maybe they have some matchmaking and my hideous performance in the first match put me in the lowest bracket?

Or... Maybe the other players were not players but really bad bots? A quick googling confirms that indeed, Epic added bots last autumn (editors note, autumn of 2019).

And there does not seem to be any obvious way to identify them, except that people seem to agree that they are quite bad. 

So turns out I am not really good at this game?... **sad trombone**

I spent some time thinking about this, and I read [an article](https://www.polygon.com/2019/10/22/20926888/fortnite-bots-skill-based-matchmaking-epic-games) discussing it. Turns out not everybody was happy with this silent introduction of bots. Making people think they are better than they are to make them enjoy the game more does seem, wrong on some level.

Or is it? What is the goal from playing games? I guess most people are not looking to become full time pro-gamers but rather just want to have some fun.

Is it wrong to let them enjoy it? Not everyone enjoys loosing. I recall playing StarCraft 2, where the matchmaking was good and made sure you lost roughly half the time by matching you against better players every time you improved. And that was not always fun. Because when you&#39;re perfectly matched it might well be that you play five games in a row and loose all of them. It was one of the reasons I did not play ladder SC2 but just fooled around in coop and other stuff.

But if we accept that developers cheat a bit to allow us to enjoy the game more. What is &#34;a bit&#34;. I am not sure there are any easy answers, it all depends.

How hard do we want our games to be? I guess the most fun part if you believe you win by a close margin.

And then again, Fortnite was not the first game to do this. Mariokart might have been one of the earlier ones to do it. It really skews the game to give worse players a chance. When you&#39;re in the lead or close to it, you will just pick up bad items. While if you&#39;re at the back you get all the good suff. And in single player the computer automatically adapted to your speed.

Asphalt 9 (at least on iPad) is also a obvious example of this, try playing a single player map with a massively over-powered car (compared to what is the recommended level on that course). Now watch how every computer keeps up/stays ahead until ~70% of the course is done. Then falls behind massively on the last 20% to make sure you win if you &#34;should&#34; win (based on the points in your car).

To conclude, I&#39;ll just say I don&#39;t know what is the right way. If the developers made it too obvious that they are letting you win, most people would not think that to be fun. But on the other hand, people like winning more than loosing.
</source:markdown>
    </item>
    
    <item>
      <title>box-art.css</title>
      <link>https://blog.nyman.re/2025/08/25/boxartcss.html</link>
      <pubDate>Mon, 25 Aug 2025 23:06:43 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/25/boxartcss.html</guid>
      <description>&lt;p&gt;Today we&amp;rsquo;re onto something lighter again. A box-art/nfo-style css, in as part of experimenting with a new look for this blog.&lt;/p&gt;
&lt;p&gt;This is harder than it looks to do in CSS, but I enjoyed the challenge.&lt;/p&gt;
&lt;p&gt;This is also something the LLM&amp;rsquo;s failed quite hard, but at least claude wrote me a simple javascript page where I could tweak the parameters until it worked.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/26224/2025/preview-nfo.png&#34; alt=&#34;Screenshot from some text, wrapped by BOX ASCII. Text is just placeholder. Dark background, green text.&#34; title=&#34;preview-nfo.png&#34; border=&#34;0&#34; width=&#34;599&#34; height=&#34;504&#34; /&gt;
&lt;p&gt;Also.. I just found this, it&amp;rsquo;s just beautiful &lt;a href=&#34;https://int10h.org&#34;&gt;int10h.org&lt;/a&gt; also this is also great &lt;a href=&#34;https://panr.github.io/terminal-css/&#34;&gt;panr.github.io/terminal-&amp;hellip;&lt;/a&gt;&lt;/p&gt;
</description>
      <source:markdown>Today we&#39;re onto something lighter again. A box-art/nfo-style css, in as part of experimenting with a new look for this blog.

This is harder than it looks to do in CSS, but I enjoyed the challenge.

This is also something the LLM&#39;s failed quite hard, but at least claude wrote me a simple javascript page where I could tweak the parameters until it worked.

&lt;img src=&#34;https://cdn.uploads.micro.blog/26224/2025/preview-nfo.png&#34; alt=&#34;Screenshot from some text, wrapped by BOX ASCII. Text is just placeholder. Dark background, green text.&#34; title=&#34;preview-nfo.png&#34; border=&#34;0&#34; width=&#34;599&#34; height=&#34;504&#34; /&gt;


Also.. I just found this, it&#39;s just beautiful [int10h.org](https://int10h.org) also this is also great [panr.github.io/terminal-...](https://panr.github.io/terminal-css/) 
</source:markdown>
    </item>
    
    <item>
      <title>Finding a charity where you can see the impact</title>
      <link>https://blog.nyman.re/2025/08/24/finding-a-charity-where-you.html</link>
      <pubDate>Sun, 24 Aug 2025 23:37:09 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/24/finding-a-charity-where-you.html</guid>
      <description>&lt;p&gt;This is a follow up to the previous one where we defined the question. &lt;a href=&#34;https://blog.nyman.re/2025/08/23/what-can-you-do.html&#34;&gt;Read that first&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Can I make a direct life saving impact to someone, where my action, with a high degree of probability, leads to the survival of someone who might otherwise not have survived.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If the action is a donation, and the answer is Yes, and I have the means. Then I want to do it.&lt;/p&gt;
&lt;p&gt;But let&amp;rsquo;s clarify what this means in practice.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It must be a monetary donation. Although volunteering is important, I am not in a place where I can do that now.&lt;/li&gt;
&lt;li&gt;The money needs to go more or less directly to the cause, preferably quite soon.&lt;/li&gt;
&lt;li&gt;There has to be some feedback from the cause.&lt;/li&gt;
&lt;li&gt;EU based which accepts IBAN or other ways of receiving donations without paying the credit card companies x%.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;3-feedback-loop&#34;&gt;3. Feedback loop&lt;/h2&gt;
&lt;p&gt;This is the main new part for my charitable donations. Previously and currently, my charitable donations go to organisations who I believe do good. But there is little or no feedback loop, so I don&amp;rsquo;t know what good my money does.&lt;/p&gt;
&lt;p&gt;I am doing this exercise, to figure out if there is a more direct way.&lt;/p&gt;
&lt;p&gt;When I started, my original idea was to find something where I donate directly to a cause. Like a heart operation for Person X, which would otherwise not happen.&lt;/p&gt;
&lt;p&gt;Based on my research, I could not find that. The closest I found was &lt;a href=&#34;http://watsi.org&#34;&gt;watsi&lt;/a&gt; which has names and pictures of the people you help. My impression is that it&amp;rsquo;s a low-income version of gofundme.&lt;/p&gt;
&lt;p&gt;After reviewing the people in need there, it does not meet my criteria for life saving. And when thinking about it, thats a great thing. After all, it would be horrible if there was a website with a list of people who would die unless they raised enough money.&lt;/p&gt;
&lt;p&gt;So I expanded my search, instead of trying to find a place with named persons, which adds a lot of overhead I started looking if there would be a way to donate to a clinic, or some other semi direct intervention.&lt;/p&gt;
&lt;h2 id=&#34;list-of-potential-charities&#34;&gt;List of potential charities&lt;/h2&gt;
&lt;p&gt;(Note, I have done only brief research on these and there is likely factual errors. It&amp;rsquo;s just my personal opinion and not facts)&lt;/p&gt;
&lt;h3 id=&#34;chain-of-hope-and-la-chaîne-de-lespoir&#34;&gt;Chain of Hope (and La Chaîne de l’Espoir)&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://www.chainofhope.org&#34;&gt;website&lt;/a&gt; &lt;a href=&#34;https://www.chainedelespoir.org/en/&#34;&gt;website&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Focusing on cardiac surgery, especially for kids. Definitely life saving but they seem quite big and from a brief check I don&amp;rsquo;t see any feedback loop.&lt;/p&gt;
&lt;h3 id=&#34;kidsor&#34;&gt;KidsOR&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://www.kidsor.org/&#34;&gt;website&lt;/a&gt;
Builds/improves Operating Rooms abroad. Ships out equipment from UK and installs it, trains the technicians and then helps maintain it. Important and valuable but seems there will be quite a long time between my donation and when it would help someone.&lt;/p&gt;
&lt;h3 id=&#34;medeor&#34;&gt;Medeor&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://medeor.de/en/&#34;&gt;website&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;German organisation focusing on providing medicine and equipment, not personnel or health interventions. Seems promising on the feedback part as it says that donations of 1000 euro and above are counted as &amp;ldquo;major donations&amp;rdquo; which receives additional feedback. As they are German, IBAN is possible.&lt;/p&gt;
&lt;h2 id=&#34;next-steps&#34;&gt;Next steps&lt;/h2&gt;
&lt;p&gt;I still have a bunch of charities to review. Finding the &amp;ldquo;perfect&amp;rdquo; charity is not easy, and it would be easier just to pick one and give. But my hopes is that I will find one I really like, which will have a tight enough feedback loop that I feel I made a difference. This in turn, will cause me to want to give more. After all, if I can help, why wouldn&amp;rsquo;t I?&lt;/p&gt;
&lt;p&gt;Currently, the answer is, because even if I donated everything I owned to the Red Cross, it would help someone, but it wouldn&amp;rsquo;t be effective use of my money.&lt;/p&gt;
&lt;p&gt;Large charities are important, they can do work that nobody else can, but small ones are also important. The big ones can&amp;rsquo;t be everywhere, and there are people in need in a lot of places.&lt;/p&gt;
&lt;p&gt;Same thing about long term vs short term donations. Both are important.&lt;/p&gt;
&lt;p&gt;Next step here will hopefully be a decision, and motivation and explanation of why. And then later, hopefully a follow up on what kind of difference it made.&lt;/p&gt;
</description>
      <source:markdown>This is a follow up to the previous one where we defined the question. [Read that first](https://blog.nyman.re/2025/08/23/what-can-you-do.html)

&gt; Can I make a direct life saving impact to someone, where my action, with a high degree of probability, leads to the survival of someone who might otherwise not have survived.

If the action is a donation, and the answer is Yes, and I have the means. Then I want to do it.

But let&#39;s clarify what this means in practice.

1. It must be a monetary donation. Although volunteering is important, I am not in a place where I can do that now.
2. The money needs to go more or less directly to the cause, preferably quite soon.
3. There has to be some feedback from the cause.
4. EU based which accepts IBAN or other ways of receiving donations without paying the credit card companies x%.

## 3. Feedback loop
This is the main new part for my charitable donations. Previously and currently, my charitable donations go to organisations who I believe do good. But there is little or no feedback loop, so I don&#39;t know what good my money does.

I am doing this exercise, to figure out if there is a more direct way.

When I started, my original idea was to find something where I donate directly to a cause. Like a heart operation for Person X, which would otherwise not happen.

Based on my research, I could not find that. The closest I found was [watsi](http://watsi.org) which has names and pictures of the people you help. My impression is that it&#39;s a low-income version of gofundme.

After reviewing the people in need there, it does not meet my criteria for life saving. And when thinking about it, thats a great thing. After all, it would be horrible if there was a website with a list of people who would die unless they raised enough money. 

So I expanded my search, instead of trying to find a place with named persons, which adds a lot of overhead I started looking if there would be a way to donate to a clinic, or some other semi direct intervention.

## List of potential charities

(Note, I have done only brief research on these and there is likely factual errors. It&#39;s just my personal opinion and not facts)

### Chain of Hope (and La Chaîne de l’Espoir)
[website](https://www.chainofhope.org) [website](https://www.chainedelespoir.org/en/)

Focusing on cardiac surgery, especially for kids. Definitely life saving but they seem quite big and from a brief check I don&#39;t see any feedback loop.

### KidsOR 

[website](https://www.kidsor.org/)
Builds/improves Operating Rooms abroad. Ships out equipment from UK and installs it, trains the technicians and then helps maintain it. Important and valuable but seems there will be quite a long time between my donation and when it would help someone. 

### Medeor
[website](https://medeor.de/en/)

German organisation focusing on providing medicine and equipment, not personnel or health interventions. Seems promising on the feedback part as it says that donations of 1000 euro and above are counted as &#34;major donations&#34; which receives additional feedback. As they are German, IBAN is possible.


## Next steps

I still have a bunch of charities to review. Finding the &#34;perfect&#34; charity is not easy, and it would be easier just to pick one and give. But my hopes is that I will find one I really like, which will have a tight enough feedback loop that I feel I made a difference. This in turn, will cause me to want to give more. After all, if I can help, why wouldn&#39;t I?

Currently, the answer is, because even if I donated everything I owned to the Red Cross, it would help someone, but it wouldn&#39;t be effective use of my money.

Large charities are important, they can do work that nobody else can, but small ones are also important. The big ones can&#39;t be everywhere, and there are people in need in a lot of places.

Same thing about long term vs short term donations. Both are important.

Next step here will hopefully be a decision, and motivation and explanation of why. And then later, hopefully a follow up on what kind of difference it made.
</source:markdown>
    </item>
    
    <item>
      <title>What can YOU do?</title>
      <link>https://blog.nyman.re/2025/08/23/what-can-you-do.html</link>
      <pubDate>Sat, 23 Aug 2025 23:58:13 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/23/what-can-you-do.html</guid>
      <description>&lt;p&gt;The world is a big place. Really big. &lt;a href=&#34;https://blog.nyman.re/2025/08/16/there-are-a-lot-of.html&#34;&gt;There are a lot of people in it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And because there are so many people, it means there are a lot of people who, at this moment needs help.&lt;/p&gt;
&lt;p&gt;So what can you do? A lot. That&amp;rsquo;s a too wide question.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s narrow it down to a very specific question.&lt;/p&gt;
&lt;p&gt;Can you save the life of someone in need?&lt;/p&gt;
&lt;p&gt;Again, yes, there are lots of ways you could, but also a lot of ways which won&amp;rsquo;t work. The question is still too wide.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s narrow it down more. Let&amp;rsquo;s focus on money.&lt;/p&gt;
&lt;p&gt;How much money would you need to spend to save the life of someone?&lt;/p&gt;
&lt;p&gt;If you ask the effective altruism people, it&amp;rsquo;s very little, just pay for some malaria nets and vaccines and the chances are that you&amp;rsquo;ll save someone.&lt;/p&gt;
&lt;p&gt;But that is very indirect. It might be a cost effective way to save a life, but it will be very hard to know if your money saved a life or not. Statistically, if you buy enough nets it will.&lt;/p&gt;
&lt;p&gt;But if you wanted to feel like you made an impact right now, or within the near future so that your giving feels more direct. And you wanted to know with a high degree of certainty that it had a impact, say within the coming three months.&lt;/p&gt;
&lt;p&gt;Now we&amp;rsquo;re close to narrowing down our question which I&amp;rsquo;ll dive in to in a coming blog post.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Can I make a direct life saving impact to someone, where my action, with a high degree of probability, leads to the survival of someone who might otherwise not have survived.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What does that cost? And what ways are there to do this.&lt;/p&gt;
</description>
      <source:markdown>The world is a big place. Really big. [There are a lot of people in it](https://blog.nyman.re/2025/08/16/there-are-a-lot-of.html).

And because there are so many people, it means there are a lot of people who, at this moment needs help.

So what can you do? A lot. That&#39;s a too wide question.

Let&#39;s narrow it down to a very specific question. 

Can you save the life of someone in need? 

Again, yes, there are lots of ways you could, but also a lot of ways which won&#39;t work. The question is still too wide. 

Let&#39;s narrow it down more. Let&#39;s focus on money.

How much money would you need to spend to save the life of someone?

If you ask the effective altruism people, it&#39;s very little, just pay for some malaria nets and vaccines and the chances are that you&#39;ll save someone. 

But that is very indirect. It might be a cost effective way to save a life, but it will be very hard to know if your money saved a life or not. Statistically, if you buy enough nets it will.

But if you wanted to feel like you made an impact right now, or within the near future so that your giving feels more direct. And you wanted to know with a high degree of certainty that it had a impact, say within the coming three months.

Now we&#39;re close to narrowing down our question which I&#39;ll dive in to in a coming blog post.

&gt; Can I make a direct life saving impact to someone, where my action, with a high degree of probability, leads to the survival of someone who might otherwise not have survived.

What does that cost? And what ways are there to do this.
</source:markdown>
    </item>
    
    <item>
      <title></title>
      <link>https://blog.nyman.re/2025/08/22/vibecoding-feels-very-productive-but.html</link>
      <pubDate>Fri, 22 Aug 2025 23:51:24 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/22/vibecoding-feels-very-productive-but.html</guid>
      <description>&lt;p&gt;vibecoding feels very productive, but often I&amp;rsquo;ve noticed it&amp;rsquo;s just a feeling, I actually know better than the LLM what the problem is, and could fix it faster, but for some reason I get stuck prompting it again and again&lt;/p&gt;
&lt;p&gt;not sure why&lt;/p&gt;
&lt;p&gt;Hopefully the psychology departments are looking at this&lt;/p&gt;
</description>
      <source:markdown>vibecoding feels very productive, but often I&#39;ve noticed it&#39;s just a feeling, I actually know better than the LLM what the problem is, and could fix it faster, but for some reason I get stuck prompting it again and again

not sure why

Hopefully the psychology departments are looking at this
</source:markdown>
    </item>
    
    <item>
      <title>My favourite podcasts</title>
      <link>https://blog.nyman.re/2025/08/21/my-favourite-podcasts.html</link>
      <pubDate>Thu, 21 Aug 2025 23:13:42 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/21/my-favourite-podcasts.html</guid>
      <description>&lt;h2 id=&#34;security-podcasts-i-enjoy&#34;&gt;Security Podcasts I Enjoy&lt;/h2&gt;
&lt;p&gt;In special order, roughly in the order of which I remembered them which says something about how much I listen to them.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://risky.biz&#34;&gt;Risky Business&lt;/a&gt; - the main feed, compact and respects my time. I haven&amp;rsquo;t missed in on a long time. They have expanded and have other great podcasts now, including a news bulletin. I listen to those sometimes.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://securityconversations.fireside.fm&#34;&gt;Three Buddy Problem&lt;/a&gt; - Quite new podcast, entertaining listen but the opposite of Risky Biz when it comes to being compact. Fun mix of Juan ranting and Costin providing deep technical commentary.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://securitycryptographywhatever.com&#34;&gt;Security Cryptography Whatever&lt;/a&gt; - Great podcast, very technical, melts my brain sometimes but I feel I learn something every episode.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://sakerhetspodcasten.se&#34;&gt;Säkerhetspodcasten&lt;/a&gt; - Swedish podcast, very entertaining, mix of somewhat serious episodes and &amp;ldquo;unstructured&amp;rdquo; episodes.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cisoseries.com/category/podcast/defense-in-depth/&#34;&gt;Defence In Depth&lt;/a&gt; - Each episode is based on a linked in discussion about some topic. Short and focused episodes. A lot of discussions from the point of CISO or security leaders. I&amp;rsquo;d recommend looking through their archive and picking a topic you&amp;rsquo;re interested in.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://darknetdiaries.com&#34;&gt;Darknet Diaries&lt;/a&gt; - The best story-telling podcast on infosec, hacking culture and internet-crime related topics. Targets a wider audience so he will explain terms and technologies in detail. Also he has strong idealogical views which he likes to share.&lt;/p&gt;
&lt;p&gt;The above are continuously running podcasts, I&amp;rsquo;ll also mention a few which are just a set of episodes focusing on one topic, and ones that has ended. Also these are not really security focused, but rather technical ones.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.economist.com/audio/podcasts/scam-inc&#34;&gt;Scam Inc&lt;/a&gt; - A podcast about the pig butchering scam centres, the scams they do and the victims. The only one on the list that is paid. You can listen to the two first ones for free to see if it&amp;rsquo;s your thing.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.bbc.co.uk/programmes/w13xtvg9/episodes/downloads&#34;&gt;The Lazarus Heist&lt;/a&gt; - About the North Korean hacking group called Lazarus Groups and their various hacks. BBC level production quality.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.bbc.co.uk/programmes/p083t547&#34;&gt;13 minutes(to the moon)&lt;/a&gt; - Started with Apollo 11 and has since expanded into related topics.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.bbc.com/audio/brand/p08llv8n&#34;&gt;The Bomb&lt;/a&gt; - Also BBC podcast, about the development of the atomic bomb.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://omegataupodcast.net&#34;&gt;Omega Tau Podcast&lt;/a&gt; - Super interesting deep-dives into various topics by interviewing people in the area. They did both German and English episodes.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.20k.org&#34;&gt;Twenty Thousand Hertz&lt;/a&gt; - A podcast about sound. The quality is what you&amp;rsquo;d expect from someone who seems obsessed with sound and audio. Has some interesting episodes. I&amp;rsquo;m definitely no audiophile but I like when experts explain things. Check out the episode on the THX Deep Note or maybe the Zelda ones.&lt;/p&gt;
&lt;p&gt;Hopefully you find something interesting here. If you&amp;rsquo;re familiar with these podcasts and have some other that you think I&amp;rsquo;d enjoy, reach out to me on &lt;a href=&#34;https://infosec.exchange/@gnyman&#34;&gt;mastodon&lt;/a&gt; or send a &lt;a href=&#34;mailto:gabriel@nyman.re&#34;&gt;email&lt;/a&gt;.&lt;/p&gt;
</description>
      <source:markdown>## Security Podcasts I Enjoy

In special order, roughly in the order of which I remembered them which says something about how much I listen to them.

[Risky Business](http://risky.biz ) - the main feed, compact and respects my time. I haven&#39;t missed in on a long time. They have expanded and have other great podcasts now, including a news bulletin. I listen to those sometimes.

[Three Buddy Problem](https://securityconversations.fireside.fm) - Quite new podcast, entertaining listen but the opposite of Risky Biz when it comes to being compact. Fun mix of Juan ranting and Costin providing deep technical commentary. 

[Security Cryptography Whatever](https://securitycryptographywhatever.com) - Great podcast, very technical, melts my brain sometimes but I feel I learn something every episode.

[Säkerhetspodcasten](https://sakerhetspodcasten.se) - Swedish podcast, very entertaining, mix of somewhat serious episodes and &#34;unstructured&#34; episodes.

[Defence In Depth](https://cisoseries.com/category/podcast/defense-in-depth/) - Each episode is based on a linked in discussion about some topic. Short and focused episodes. A lot of discussions from the point of CISO or security leaders. I&#39;d recommend looking through their archive and picking a topic you&#39;re interested in.

[Darknet Diaries](http://darknetdiaries.com) - The best story-telling podcast on infosec, hacking culture and internet-crime related topics. Targets a wider audience so he will explain terms and technologies in detail. Also he has strong idealogical views which he likes to share.


The above are continuously running podcasts, I&#39;ll also mention a few which are just a set of episodes focusing on one topic, and ones that has ended. Also these are not really security focused, but rather technical ones.

[Scam Inc](https://www.economist.com/audio/podcasts/scam-inc) - A podcast about the pig butchering scam centres, the scams they do and the victims. The only one on the list that is paid. You can listen to the two first ones for free to see if it&#39;s your thing.

[The Lazarus Heist](https://www.bbc.co.uk/programmes/w13xtvg9/episodes/downloads) - About the North Korean hacking group called Lazarus Groups and their various hacks. BBC level production quality. 

[13 minutes(to the moon)](https://www.bbc.co.uk/programmes/p083t547) - Started with Apollo 11 and has since expanded into related topics. 

[The Bomb](https://www.bbc.com/audio/brand/p08llv8n) - Also BBC podcast, about the development of the atomic bomb.

[Omega Tau Podcast](https://omegataupodcast.net) - Super interesting deep-dives into various topics by interviewing people in the area. They did both German and English episodes.

[Twenty Thousand Hertz](https://www.20k.org) - A podcast about sound. The quality is what you&#39;d expect from someone who seems obsessed with sound and audio. Has some interesting episodes. I&#39;m definitely no audiophile but I like when experts explain things. Check out the episode on the THX Deep Note or maybe the Zelda ones.


Hopefully you find something interesting here. If you&#39;re familiar with these podcasts and have some other that you think I&#39;d enjoy, reach out to me on [mastodon](https://infosec.exchange/@gnyman) or send a [email](mailto:gabriel@nyman.re).
</source:markdown>
    </item>
    
    <item>
      <title>A simple devcontainer for your agent with eyes (browser and screenshot capabilities)</title>
      <link>https://blog.nyman.re/2025/08/20/a-simple-devcontainer-for-your.html</link>
      <pubDate>Wed, 20 Aug 2025 22:52:49 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/20/a-simple-devcontainer-for-your.html</guid>
      <description>&lt;p&gt;These instructions have been tested on a M1 MacBook with podman, your mileage may vary. Note that running playwright/chrome as root might be dangerous so don&amp;rsquo;t use this for scraping or untrusted content unless you know what you&amp;rsquo;re doing.&lt;/p&gt;
&lt;p&gt;Actually, never feed untrusted content into your LLM and always sandbox it as much as possible. Otherwise you will sooner or later be a sad panda.&lt;/p&gt;
&lt;h2 id=&#34;instruction&#34;&gt;Instruction&lt;/h2&gt;
&lt;p&gt;Put this into &lt;code&gt;.devcontainer/devcontainer.json&lt;/code&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Playwright Dev Environment&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;image&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mcr.microsoft.com/playwright:v1.55.0-noble&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;postCreateCommand&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;npm install -g @anthropic-ai/claude-code&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;customizations&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;vscode&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;extensions&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Anthropic.claude-code&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;//&amp;#34;remoteUser&amp;#34;: &amp;#34;pwuser&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;runArgs&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;--ipc=host&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;--security-opt=seccomp=unconfined&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;capAdd&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SYS_ADMIN&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;mounts&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;source=claude-code-bashhistory-${devcontainerId},target=/commandhistory,type=volume&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;source=claude-code-config-${devcontainerId},target=/home/pwuser/.claude,type=volume&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;workspaceMount&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;workspaceFolder&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/workspace&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;containerEnv&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;NODE_OPTIONS&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;--max-old-space-size=4096&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;CLAUDE_CONFIG_DIR&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/home/pwuser/.claude&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then create something like this (or ask your llm to do it)&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#!/usr/bin/env node
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Playwright-based headless renderer with console capture
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Usage:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;//   node pw-screenshot.mjs --url https://example.com --out out.png
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;//   node pw-screenshot.mjs --html-file page.html --out out.png
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;//   echo &amp;#34;&amp;lt;h1&amp;gt;Hello&amp;lt;/h1&amp;gt;&amp;#34; | node pw-screenshot.mjs --html-stdin --out out.png
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fs&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;node:fs&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;path&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;node:path&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;process&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;node:process&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;import&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;chromium&lt;/span&gt; } &lt;span style=&#34;color:#a6e22e&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;playwright&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parseArgs&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;argv&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;url&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;htmlFile&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;htmlStdin&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;screenshot.png&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;wait&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;networkidle&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;// playwright: load | domcontentloaded | networkidle
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;fullPage&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;timeout&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;60000&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;viewport&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;1280x800&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;emulateMedia&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;argv&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;k&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;argv&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;argv&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;k&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--url&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;url&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--html-file&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;htmlFile&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--html-stdin&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;htmlStdin&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--out&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--wait&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;wait&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;normalizeWait&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;); &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--fullpage&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fullPage&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;false&amp;#39;&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;false&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--no-fullpage&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fullPage&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--timeout&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;timeout&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Number(&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;); &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--viewport&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;viewport&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--console&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--emulate-media&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;emulateMedia&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--help&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;-h&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;printHelp&lt;/span&gt;(); &lt;span style=&#34;color:#a6e22e&#34;&gt;process&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;exit&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;normalizeWait&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;val&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; String(&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;toLowerCase&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;val&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;networkidle0&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;val&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;networkidle2&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;networkidle&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;val&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;domcontentloaded&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;val&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;load&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;val&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;networkidle&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;val&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Error(&lt;span style=&#34;color:#e6db74&#34;&gt;`Invalid --wait &amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;. Use load|domcontentloaded|networkidle`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;printHelp&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;help&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;Usage:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  node pw-screenshot.mjs [--url URL | --html-file FILE | --html-stdin] [--out PATH]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;Options:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  --url URL            Navigate to the URL and render.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  --html-file FILE     Load HTML from file and render.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  --html-stdin         Read HTML from stdin and render.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  --out PATH           Screenshot output path (default: screenshot.png).
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  --wait MODE          load | domcontentloaded | networkidle (default: networkidle).
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  --fullpage BOOL      true/false; or --no-fullpage (default: true).
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  --timeout MS         Navigation/content timeout in ms (default: 60000).
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  --viewport WxH       Viewport, e.g., 1280x800 (default: 1280x800).
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  --console PATH       Write page console JSONL to PATH or &amp;#39;-&amp;#39; for stdout (default: stderr only).
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  --emulate-media TYPE Emulate media type: screen | print.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  --help               Show this help.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;process&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stderr&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;write&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;help&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parseViewport&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;spec&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; String(&lt;span style=&#34;color:#a6e22e&#34;&gt;spec&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;toLowerCase&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;trim&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;match&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;/^(\d+)x(\d+)$/&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Error(&lt;span style=&#34;color:#e6db74&#34;&gt;`Invalid --viewport &amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;spec&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;. Expected WxH, e.g., 1280x800`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;width&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; Number(&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]), &lt;span style=&#34;color:#a6e22e&#34;&gt;height&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; Number(&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]) };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;openConsoleStream&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;dest&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;dest&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;dest&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;-&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;process&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stdout&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fs&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;createWriteStream&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;dest&lt;/span&gt;, { &lt;span style=&#34;color:#a6e22e&#34;&gt;flags&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;a&amp;#39;&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;writeConsole&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;stream&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;record&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;line&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;JSON&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stringify&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;record&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\n&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;stream&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;stream&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;write&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;line&lt;/span&gt;); &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;process&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stderr&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;write&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;`[console] &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;record&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;record&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;text&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;\n`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;readStdin&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Promise(&lt;span style=&#34;color:#a6e22e&#34;&gt;resolve&lt;/span&gt; =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;process&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stdin&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;setEncoding&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;utf8&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;process&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stdin&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;on&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;data&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;chunk&lt;/span&gt; =&amp;gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;chunk&lt;/span&gt;; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;process&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stdin&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;on&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;end&amp;#39;&lt;/span&gt;, () =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;resolve&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;data&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parseArgs&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;process&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;argv&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;url&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;htmlFile&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;htmlStdin&lt;/span&gt;)) { &lt;span style=&#34;color:#a6e22e&#34;&gt;printHelp&lt;/span&gt;(); &lt;span style=&#34;color:#a6e22e&#34;&gt;process&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;exit&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;); }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;viewport&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parseViewport&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;viewport&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;consoleStream&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;openConsoleStream&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;browser&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;browser&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;chromium&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;launch&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;headless&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--no-sandbox&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--disable-dev-shm-usage&amp;#39;&lt;/span&gt;] });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;browser&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;newContext&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;viewport&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;page&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;newPage&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;page&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;on&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;console&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;msg&lt;/span&gt; =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;writeConsole&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;consoleStream&lt;/span&gt;, { &lt;span style=&#34;color:#a6e22e&#34;&gt;ts&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Date().&lt;span style=&#34;color:#a6e22e&#34;&gt;toISOString&lt;/span&gt;(), &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;msg&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;(), &lt;span style=&#34;color:#a6e22e&#34;&gt;text&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;msg&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;text&lt;/span&gt;() });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;page&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;on&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pageerror&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;writeConsole&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;consoleStream&lt;/span&gt;, { &lt;span style=&#34;color:#a6e22e&#34;&gt;ts&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Date().&lt;span style=&#34;color:#a6e22e&#34;&gt;toISOString&lt;/span&gt;(), &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pageerror&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;text&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; String(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;) });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;emulateMedia&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;page&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;emulateMedia&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;media&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;emulateMedia&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;url&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;page&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;goto&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;url&lt;/span&gt;, { &lt;span style=&#34;color:#a6e22e&#34;&gt;waitUntil&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;wait&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;timeout&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;timeout&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;html&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;htmlFile&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fs&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;readFileSync&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;path&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;resolve&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;htmlFile&lt;/span&gt;), &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;utf8&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;readStdin&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;page&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;setContent&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;html&lt;/span&gt;, { &lt;span style=&#34;color:#a6e22e&#34;&gt;waitUntil&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;wait&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;timeout&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;timeout&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;page&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;screenshot&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;path&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;fullPage&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fullPage&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;process&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stdout&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;write&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;`Saved screenshot to &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;\n`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#66d9ef&#34;&gt;finally&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;browser&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;browser&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;close&lt;/span&gt;().&lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt;(() =&amp;gt; {});
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;consoleStream&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;consoleStream&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;process&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stdout&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;consoleStream&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;end&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;().&lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; =&amp;gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;process&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stderr&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;write&lt;/span&gt;(String(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stack&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\n&amp;#39;&lt;/span&gt;); &lt;span style=&#34;color:#a6e22e&#34;&gt;process&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;exit&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;); });
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After that you can instruct claude or some other coding agent (that can view images, e.g. not codex currently) to view the result and describe it.&lt;/p&gt;
&lt;p&gt;Hopefully this makes the agents more useful as they can inspect and see if the change they did actually had the expected result.&lt;/p&gt;
&lt;p&gt;I have not tested this very much yet, but it&amp;rsquo;s one of the pet issues I&amp;rsquo;ve when having agents do any changes to a webpage. The lack of feedback loop for them means I&amp;rsquo;ve had to view and explain the result.&lt;/p&gt;
</description>
      <source:markdown>These instructions have been tested on a M1 MacBook with podman, your mileage may vary. Note that running playwright/chrome as root might be dangerous so don&#39;t use this for scraping or untrusted content unless you know what you&#39;re doing.

Actually, never feed untrusted content into your LLM and always sandbox it as much as possible. Otherwise you will sooner or later be a sad panda.

## Instruction

Put this into `.devcontainer/devcontainer.json`

```json
{
  &#34;name&#34;: &#34;Playwright Dev Environment&#34;,
  &#34;image&#34;: &#34;mcr.microsoft.com/playwright:v1.55.0-noble&#34;,
  &#34;postCreateCommand&#34;: &#34;npm install -g @anthropic-ai/claude-code&#34;,
  &#34;customizations&#34;: {
    &#34;vscode&#34;: {
      &#34;extensions&#34;: [
        &#34;Anthropic.claude-code&#34;
      ]
    }
  },
  //&#34;remoteUser&#34;: &#34;pwuser&#34;,
  &#34;runArgs&#34;: [
    &#34;--ipc=host&#34;,
    &#34;--security-opt=seccomp=unconfined&#34;
  ],
  &#34;capAdd&#34;: [
    &#34;SYS_ADMIN&#34;
  ],
  &#34;mounts&#34;: [
    &#34;source=claude-code-bashhistory-${devcontainerId},target=/commandhistory,type=volume&#34;,
    &#34;source=claude-code-config-${devcontainerId},target=/home/pwuser/.claude,type=volume&#34;
  ],
  &#34;workspaceMount&#34;: &#34;source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated&#34;,
  &#34;workspaceFolder&#34;: &#34;/workspace&#34;,
  &#34;containerEnv&#34;: {
    &#34;NODE_OPTIONS&#34;: &#34;--max-old-space-size=4096&#34;,
    &#34;CLAUDE_CONFIG_DIR&#34;: &#34;/home/pwuser/.claude&#34;
  }
}
```

Then create something like this (or ask your llm to do it)

```js
#!/usr/bin/env node
// Playwright-based headless renderer with console capture
// Usage:
//   node pw-screenshot.mjs --url https://example.com --out out.png
//   node pw-screenshot.mjs --html-file page.html --out out.png
//   echo &#34;&lt;h1&gt;Hello&lt;/h1&gt;&#34; | node pw-screenshot.mjs --html-stdin --out out.png

import fs from &#39;node:fs&#39;;
import path from &#39;node:path&#39;;
import process from &#39;node:process&#39;;
import { chromium } from &#39;playwright&#39;;

function parseArgs(argv) {
  const args = {
    url: null,
    htmlFile: null,
    htmlStdin: false,
    out: &#39;screenshot.png&#39;,
    wait: &#39;networkidle&#39;, // playwright: load | domcontentloaded | networkidle
    fullPage: true,
    timeout: 60000,
    viewport: &#39;1280x800&#39;,
    console: null,
    emulateMedia: null,
  };
  for (let i = 2; i &lt; argv.length; i++) {
    const k = argv[i];
    const v = argv[i + 1];
    switch (k) {
      case &#39;--url&#39;: args.url = v; i++; break;
      case &#39;--html-file&#39;: args.htmlFile = v; i++; break;
      case &#39;--html-stdin&#39;: args.htmlStdin = true; break;
      case &#39;--out&#39;: args.out = v; i++; break;
      case &#39;--wait&#39;: args.wait = normalizeWait(v); i++; break;
      case &#39;--fullpage&#39;: args.fullPage = v !== &#39;false&#39;; if (v === &#39;false&#39;) i++; break;
      case &#39;--no-fullpage&#39;: args.fullPage = false; break;
      case &#39;--timeout&#39;: args.timeout = Number(v); i++; break;
      case &#39;--viewport&#39;: args.viewport = v; i++; break;
      case &#39;--console&#39;: args.console = v; i++; break;
      case &#39;--emulate-media&#39;: args.emulateMedia = v; i++; break;
      case &#39;--help&#39;:
      case &#39;-h&#39;: printHelp(); process.exit(0);
      default: break;
    }
  }
  return args;
}

function normalizeWait(v) {
  const val = String(v).toLowerCase();
  if (val === &#39;networkidle0&#39; || val === &#39;networkidle2&#39;) return &#39;networkidle&#39;;
  if (val === &#39;domcontentloaded&#39; || val === &#39;load&#39; || val === &#39;networkidle&#39;) return val;
  throw new Error(`Invalid --wait &#39;${v}&#39;. Use load|domcontentloaded|networkidle`);
}

function printHelp() {
  const help = `
Usage:
  node pw-screenshot.mjs [--url URL | --html-file FILE | --html-stdin] [--out PATH]

Options:
  --url URL            Navigate to the URL and render.
  --html-file FILE     Load HTML from file and render.
  --html-stdin         Read HTML from stdin and render.
  --out PATH           Screenshot output path (default: screenshot.png).
  --wait MODE          load | domcontentloaded | networkidle (default: networkidle).
  --fullpage BOOL      true/false; or --no-fullpage (default: true).
  --timeout MS         Navigation/content timeout in ms (default: 60000).
  --viewport WxH       Viewport, e.g., 1280x800 (default: 1280x800).
  --console PATH       Write page console JSONL to PATH or &#39;-&#39; for stdout (default: stderr only).
  --emulate-media TYPE Emulate media type: screen | print.
  --help               Show this help.
`;
  process.stderr.write(help);
}

function parseViewport(spec) {
  const m = String(spec).toLowerCase().trim().match(/^(\d+)x(\d+)$/);
  if (!m) throw new Error(`Invalid --viewport &#39;${spec}&#39;. Expected WxH, e.g., 1280x800`);
  return { width: Number(m[1]), height: Number(m[2]) };
}

function openConsoleStream(dest) {
  if (!dest) return null;
  if (dest === &#39;-&#39;) return process.stdout;
  return fs.createWriteStream(dest, { flags: &#39;a&#39; });
}

function writeConsole(stream, record) {
  const line = JSON.stringify(record) + &#39;\n&#39;;
  if (stream) stream.write(line); else process.stderr.write(`[console] ${record.type} ${record.text}\n`);
}

async function readStdin() {
  return await new Promise(resolve =&gt; {
    let data = &#39;&#39;;
    process.stdin.setEncoding(&#39;utf8&#39;);
    process.stdin.on(&#39;data&#39;, chunk =&gt; { data += chunk; });
    process.stdin.on(&#39;end&#39;, () =&gt; resolve(data));
  });
}

async function main() {
  const args = parseArgs(process.argv);
  if (!(args.url || args.htmlFile || args.htmlStdin)) { printHelp(); process.exit(2); }

  const viewport = parseViewport(args.viewport);
  const consoleStream = openConsoleStream(args.console);

  let browser;
  try {
    browser = await chromium.launch({ headless: true, args: [&#39;--no-sandbox&#39;, &#39;--disable-dev-shm-usage&#39;] });
    const context = await browser.newContext({ viewport });
    const page = await context.newPage();

    page.on(&#39;console&#39;, msg =&gt; {
      writeConsole(consoleStream, { ts: new Date().toISOString(), type: msg.type(), text: msg.text() });
    });
    page.on(&#39;pageerror&#39;, err =&gt; {
      writeConsole(consoleStream, { ts: new Date().toISOString(), type: &#39;pageerror&#39;, text: String(err) });
    });

    if (args.emulateMedia) await page.emulateMedia({ media: args.emulateMedia });

    if (args.url) {
      await page.goto(args.url, { waitUntil: args.wait, timeout: args.timeout });
    } else {
      const html = args.htmlFile ? fs.readFileSync(path.resolve(args.htmlFile), &#39;utf8&#39;) : await readStdin();
      await page.setContent(html, { waitUntil: args.wait, timeout: args.timeout });
    }

    await page.screenshot({ path: args.out, fullPage: !!args.fullPage });
    process.stdout.write(`Saved screenshot to ${args.out}\n`);
  } finally {
    if (browser) await browser.close().catch(() =&gt; {});
    if (consoleStream &amp;&amp; consoleStream !== process.stdout) consoleStream.end();
  }
}

main().catch(err =&gt; { process.stderr.write(String(err.stack || err) + &#39;\n&#39;); process.exit(1); });
```

After that you can instruct claude or some other coding agent (that can view images, e.g. not codex currently) to view the result and describe it.

Hopefully this makes the agents more useful as they can inspect and see if the change they did actually had the expected result. 

I have not tested this very much yet, but it&#39;s one of the pet issues I&#39;ve when having agents do any changes to a webpage. The lack of feedback loop for them means I&#39;ve had to view and explain the result.
</source:markdown>
    </item>
    
    <item>
      <title>You cannot hide on the internet</title>
      <link>https://blog.nyman.re/2025/08/19/you-cannot-hide-on-the.html</link>
      <pubDate>Tue, 19 Aug 2025 21:48:49 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/19/you-cannot-hide-on-the.html</guid>
      <description>&lt;p&gt;At least not on the IPv4 network, but I would not trust the IPv6 network either, and you have not been able to for a long time.&lt;/p&gt;
&lt;p&gt;If you open a port to the whole world, it will get probed. If it&amp;rsquo;s a popular port like 443 or a sensitive one like 9200, it will get scanned really-fast.&lt;/p&gt;
&lt;p&gt;Same goes if you announce it by creating a TLS certificate with a ACME service like Let&amp;rsquo;s Encrypt. When you do, Let&amp;rsquo;s Encrypt will publish your certificate on the Certificate Transparency log. And there are multiple entities who sit and watch it and will probe everything on it.&lt;/p&gt;
&lt;p&gt;I learned just how fast recently when I was playing around with tailscale funnel, &lt;a href=&#34;https://infosec.exchange/@gnyman/112167520978474642&#34;&gt;within minutes I had someone from a Russian IP range sending requests my way&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So the tl.dr. is don&amp;rsquo;t open any services to the wider internet unless you are sure they are safe. If they are more complicated or less battle tested than ssh and nginx, you are better of not exposing it.&lt;/p&gt;
&lt;p&gt;Instead use &lt;a href=&#34;https://netbird.io/&#34;&gt;netbird&lt;/a&gt;, fireguard, tailscale, zerotier to access things. Or if you can&amp;rsquo;t or does not want to, hide it behind wildcard TLS certs, or some other way.&lt;/p&gt;
</description>
      <source:markdown>At least not on the IPv4 network, but I would not trust the IPv6 network either, and you have not been able to for a long time.

If you open a port to the whole world, it will get probed. If it&#39;s a popular port like 443 or a sensitive one like 9200, it will get scanned really-fast.

Same goes if you announce it by creating a TLS certificate with a ACME service like Let&#39;s Encrypt. When you do, Let&#39;s Encrypt will publish your certificate on the Certificate Transparency log. And there are multiple entities who sit and watch it and will probe everything on it.

I learned just how fast recently when I was playing around with tailscale funnel, [within minutes I had someone from a Russian IP range sending requests my way](https://infosec.exchange/@gnyman/112167520978474642).

So the tl.dr. is don&#39;t open any services to the wider internet unless you are sure they are safe. If they are more complicated or less battle tested than ssh and nginx, you are better of not exposing it.

Instead use [netbird](https://netbird.io/), fireguard, tailscale, zerotier to access things. Or if you can&#39;t or does not want to, hide it behind wildcard TLS certs, or some other way.
</source:markdown>
    </item>
    
    <item>
      <title>LUKS on NVMe: From 40 GiB/s to 4, Then Back to 20 GiB/s</title>
      <link>https://blog.nyman.re/2025/08/18/luks-on-nvme-from-gibs.html</link>
      <pubDate>Mon, 18 Aug 2025 13:41:34 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/18/luks-on-nvme-from-gibs.html</guid>
      <description>&lt;p&gt;&lt;em&gt;Note: This testing described in this post was done over a year ago. It might be that things changed since then.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;At work, we recently upgraded our PostgreSQL servers. This time, however, we encountered an unexpected roadblock when attempting to enable full disk encryption (FDE) with LUKS - our standard deployment. In past benchmarks, enabling LUKS full-disk encryption cost us ~10%. This time, it left us with only 10% of our throughput - a 90% drop.&lt;/p&gt;
&lt;p&gt;So a bit of background, the new server is the most capable server I’ve benchmarked to date. 12 Intel P5520 NVMe disks and two 48-core Intel Xeon Sapphire Rapids CPU. Enough horsepower to handle our workloads for the foreseeable future.&lt;/p&gt;
&lt;p&gt;With the 12 disks in a RAID10 configuration, we were happy with the raw performance figures: sequential write speeds of around 20 GiB/s and read speeds of 40 GiB/s (note gigabytes, not gigabits).&lt;/p&gt;
&lt;p&gt;But our joy was short-lived. As soon as we added the LUKS encryption, performance plummeted. This was not at all what we expected, we had not even planned to do much benchmarking because previous synthetic benchmarking had only shown a 10% decrease, which in reality meant no real world impact.&lt;/p&gt;
&lt;p&gt;But I had always had the understanding that modern cryptography is so fast that encryption won&amp;rsquo;t add any overhead. Most of it probably stems from the push for TLS and &lt;a href=&#34;http://istlsfastyet.com/&#34;&gt;http://istlsfastyet.com/&lt;/a&gt;. So it might be wrong.&lt;/p&gt;
&lt;p&gt;Fortunately, we weren&amp;rsquo;t the first to encounter such a noticeable impact. When researching, we found that Cloudflare &lt;a href=&#34;https://blog.cloudflare.com/speeding-up-linux-disk-encryption/&#34;&gt;had previously grappled&lt;/a&gt; with a similar problem and contributed a fix that was eventually merged into the Linux kernel. Enabling this fix reduced CPU usage to near-zero levels. However, surprisingly, it did little to improve the overall I/O performance.&lt;/p&gt;
&lt;p&gt;As I had personally told many that encryption is so fast that there is no reason not to do it, even if the data wouldn&amp;rsquo;t be sensitive I felt I had to get to the bottom of this. So we set up a more comprehensive testing framework with various options to see if we could figure out how to improve the speeds. We also added a wildcard: the bcachefs filesystem which seemed promising and had some interesting features, like using an NVMe/SSD as read/write cache for spinning disks.&lt;/p&gt;
&lt;p&gt;Our test setup consisted of a 10-disk RAID10 array of Intel P5520 NVMe disks, divided into eight equal partitions spread across all ten disks. The reason for not using all 12 was that we planned to do some single disk testing also, and with the unencrypted file system the raid10 scaled quite linearly. We decided the results of the 10-disk tests should apply to a 12-disk raid10 as well.&lt;/p&gt;
&lt;p&gt;These were the options we decided to explore&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Plain ext4&lt;/li&gt;
&lt;li&gt;Plain LUKS&lt;/li&gt;
&lt;li&gt;LUKS with &lt;code&gt;--sector-size 4096&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;LUKS with Cloudflare patches&lt;/li&gt;
&lt;li&gt;LUKS with Cloudflare patches and &lt;code&gt;--sector-size 4096&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Bcachefs&lt;/li&gt;
&lt;li&gt;Bcachefs with encryption&lt;/li&gt;
&lt;li&gt;LUKS with &lt;code&gt;--sector-size 4096&lt;/code&gt; and &lt;code&gt;--perf-same_cpu_crypt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;LUKS with Cloudflare patches, &lt;code&gt;--sector-size 4096&lt;/code&gt;, and &lt;code&gt;--perf-same_cpu_crypt&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The server was running the pre-release of Ubuntu 24.04 which was available in March 2024 (in order to get a new kernel 6.8 which supports bcachefs). To test these configurations, we ran four different FIO tests (&lt;a href=&#34;https://gist.github.com/gnyman/a2c238de452dd2cf31cb82dfe9bccd89&#34;&gt;gist with the .fio&lt;/a&gt;) on each of the targets. The tests are designed to test sequential read and write speeds, as well as random small reads and writes to see how many IOPS we can get out of them. While the test parameters might not be optimal (if you have ideas how to improve them, let me know) we stuck with them because it was the same settings we used many years ago, so it gives us an idea how things have improved.&lt;/p&gt;
&lt;h3 id=&#34;the-results&#34;&gt;The Results&lt;/h3&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/26224/2025/write-bw-kib-s.png&#34; alt=&#34;Write BW (kib_s).&#34; title=&#34;Write BW (kib_s).png&#34; border=&#34;0&#34; width=&#34;600&#34; height=&#34;371&#34; /&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/26224/2025/read-bw-kib-s.png&#34; alt=&#34;Read BW (kib_s).&#34; title=&#34;Read BW (kib_s).png&#34; border=&#34;0&#34; width=&#34;600&#34; height=&#34;371&#34; /&gt;
&lt;p&gt;You can view the full fio results and raw numbers in the &lt;a href=&#34;https://docs.google.com/spreadsheets/d/1PXCN14d77Ey7e2nd0y4dmloqY8nhvbUjaMDQ8OOJFV0/edit?usp=sharing&#34;&gt;google sheet here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Looking at the graph, the key setting was the sector-size. This change brought us up to around 50% of the raw disk speeds – a substantial improvement.&lt;/p&gt;
&lt;p&gt;All the other options we tried for LUKS seemed to have small to no measurable differences.&lt;/p&gt;
&lt;p&gt;While we are not experts on the inner workings of LUKS, it does seem to make sense that a bigger block size would speed things up. Our interpretation is that the bottleneck is not the raw encryption but rather somewhere in the process of getting the blocks lined up for encryption. Therefore, reducing the number of blocks eightfold speeds things up significantly.&lt;/p&gt;
&lt;p&gt;The younger bcachefs filesystem, did not manage to keep up with the encryption enabled. Our working theory is that since it uses ChaCha20/Poly1305 for encryption, which, while being a fast algorithm, does not benefit from specialized hardware instructions like AES does. It might be that &lt;a href=&#34;https://www.intel.com/content/www/us/en/support/articles/000093843/technologies/intel-quickassist-technology-intel-qat.html&#34;&gt;Intel QAT&lt;/a&gt; could help but we did not have time to look into that.&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;In the end, we settled on using LUKS with &lt;code&gt;--sector-size 4096&lt;/code&gt;, the Cloudflare patches enabled, and &lt;code&gt;--perf-same_cpu_crypt&lt;/code&gt;. While not achieving the same great speeds as using RAW, this configuration provided us with good enough performance to meet our needs while allowing us to maintain the security benefits of FDE.&lt;/p&gt;
&lt;p&gt;We also explored the possibility of leveraging the built-in encryption capabilities of the disks through the Trusted Computing Group&amp;rsquo;s OPAL specification. Unfortunately, our specific NVMe models lacked support for OPAL or any other form of disk-level encryption.&lt;/p&gt;
&lt;p&gt;The key lesson: always benchmark, even if it&amp;rsquo;s just a quick one. Defaults can leave enormous performance on the table, and the fix may be as simple as a sector-size flag.&lt;/p&gt;
&lt;p&gt;Special thanks to Björn D. and Niclas J. for their assistance with both the testing and giving feedback on this post.&lt;/p&gt;
</description>
      <source:markdown>*Note: This testing described in this post was done over a year ago. It might be that things changed since then.*

At work, we recently upgraded our PostgreSQL servers. This time, however, we encountered an unexpected roadblock when attempting to enable full disk encryption (FDE) with LUKS - our standard deployment. In past benchmarks, enabling LUKS full-disk encryption cost us ~10%. This time, it left us with only 10% of our throughput - a 90% drop.

So a bit of background, the new server is the most capable server I’ve benchmarked to date. 12 Intel P5520 NVMe disks and two 48-core Intel Xeon Sapphire Rapids CPU. Enough horsepower to handle our workloads for the foreseeable future. 

With the 12 disks in a RAID10 configuration, we were happy with the raw performance figures: sequential write speeds of around 20 GiB/s and read speeds of 40 GiB/s (note gigabytes, not gigabits). 

But our joy was short-lived. As soon as we added the LUKS encryption, performance plummeted. This was not at all what we expected, we had not even planned to do much benchmarking because previous synthetic benchmarking had only shown a 10% decrease, which in reality meant no real world impact.

But I had always had the understanding that modern cryptography is so fast that encryption won&#39;t add any overhead. Most of it probably stems from the push for TLS and http://istlsfastyet.com/. So it might be wrong.

Fortunately, we weren&#39;t the first to encounter such a noticeable impact. When researching, we found that Cloudflare [had previously grappled](https://blog.cloudflare.com/speeding-up-linux-disk-encryption/) with a similar problem and contributed a fix that was eventually merged into the Linux kernel. Enabling this fix reduced CPU usage to near-zero levels. However, surprisingly, it did little to improve the overall I/O performance.

As I had personally told many that encryption is so fast that there is no reason not to do it, even if the data wouldn&#39;t be sensitive I felt I had to get to the bottom of this. So we set up a more comprehensive testing framework with various options to see if we could figure out how to improve the speeds. We also added a wildcard: the bcachefs filesystem which seemed promising and had some interesting features, like using an NVMe/SSD as read/write cache for spinning disks.

Our test setup consisted of a 10-disk RAID10 array of Intel P5520 NVMe disks, divided into eight equal partitions spread across all ten disks. The reason for not using all 12 was that we planned to do some single disk testing also, and with the unencrypted file system the raid10 scaled quite linearly. We decided the results of the 10-disk tests should apply to a 12-disk raid10 as well.

These were the options we decided to explore

- Plain ext4
- Plain LUKS
- LUKS with `--sector-size 4096`
- LUKS with Cloudflare patches
- LUKS with Cloudflare patches and `--sector-size 4096`
- Bcachefs
- Bcachefs with encryption
- LUKS with `--sector-size 4096` and `--perf-same_cpu_crypt`
- LUKS with Cloudflare patches, `--sector-size 4096`, and `--perf-same_cpu_crypt`

The server was running the pre-release of Ubuntu 24.04 which was available in March 2024 (in order to get a new kernel 6.8 which supports bcachefs). To test these configurations, we ran four different FIO tests ([gist with the .fio](https://gist.github.com/gnyman/a2c238de452dd2cf31cb82dfe9bccd89)) on each of the targets. The tests are designed to test sequential read and write speeds, as well as random small reads and writes to see how many IOPS we can get out of them. While the test parameters might not be optimal (if you have ideas how to improve them, let me know) we stuck with them because it was the same settings we used many years ago, so it gives us an idea how things have improved.

### The Results
&lt;img src=&#34;https://cdn.uploads.micro.blog/26224/2025/write-bw-kib-s.png&#34; alt=&#34;Write BW (kib_s).&#34; title=&#34;Write BW (kib_s).png&#34; border=&#34;0&#34; width=&#34;600&#34; height=&#34;371&#34; /&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/26224/2025/read-bw-kib-s.png&#34; alt=&#34;Read BW (kib_s).&#34; title=&#34;Read BW (kib_s).png&#34; border=&#34;0&#34; width=&#34;600&#34; height=&#34;371&#34; /&gt;

You can view the full fio results and raw numbers in the [google sheet here](https://docs.google.com/spreadsheets/d/1PXCN14d77Ey7e2nd0y4dmloqY8nhvbUjaMDQ8OOJFV0/edit?usp=sharing).

Looking at the graph, the key setting was the sector-size. This change brought us up to around 50% of the raw disk speeds – a substantial improvement.

All the other options we tried for LUKS seemed to have small to no measurable differences.

While we are not experts on the inner workings of LUKS, it does seem to make sense that a bigger block size would speed things up. Our interpretation is that the bottleneck is not the raw encryption but rather somewhere in the process of getting the blocks lined up for encryption. Therefore, reducing the number of blocks eightfold speeds things up significantly.

The younger bcachefs filesystem, did not manage to keep up with the encryption enabled. Our working theory is that since it uses ChaCha20/Poly1305 for encryption, which, while being a fast algorithm, does not benefit from specialized hardware instructions like AES does. It might be that [Intel QAT](https://www.intel.com/content/www/us/en/support/articles/000093843/technologies/intel-quickassist-technology-intel-qat.html) could help but we did not have time to look into that.

### Conclusion

In the end, we settled on using LUKS with `--sector-size 4096`, the Cloudflare patches enabled, and `--perf-same_cpu_crypt`. While not achieving the same great speeds as using RAW, this configuration provided us with good enough performance to meet our needs while allowing us to maintain the security benefits of FDE.

We also explored the possibility of leveraging the built-in encryption capabilities of the disks through the Trusted Computing Group&#39;s OPAL specification. Unfortunately, our specific NVMe models lacked support for OPAL or any other form of disk-level encryption.

The key lesson: always benchmark, even if it&#39;s just a quick one. Defaults can leave enormous performance on the table, and the fix may be as simple as a sector-size flag.

Special thanks to Björn D. and Niclas J. for their assistance with both the testing and giving feedback on this post.
</source:markdown>
    </item>
    
    <item>
      <title>Internet is big but we humans are not ready for it</title>
      <link>https://blog.nyman.re/2025/08/17/internet-is-big-but-we.html</link>
      <pubDate>Sun, 17 Aug 2025 22:00:56 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/17/internet-is-big-but-we.html</guid>
      <description>&lt;p&gt;I thought it was crazy to think about all the 8.2 billion people.&lt;/p&gt;
&lt;p&gt;A even crazier thought is how many internet users there are, who in theory can talk to anyone else.&lt;/p&gt;
&lt;p&gt;From an information point of view it&amp;rsquo;s just a amazing amount of informationand potential available.&lt;/p&gt;
&lt;p&gt;Of course, not everyone has something to say to every other user. But the fact that people who are interested in a subject can &amp;ldquo;easily&amp;rdquo; talk to so many likeminded people should allow for humanity to develop at a never-before seen pace.&lt;/p&gt;
&lt;p&gt;Are we? I don&amp;rsquo;t know. Feels like things are stagnating, partially because of bad incentives. Or just human nature not having caught up with things.&lt;/p&gt;
&lt;p&gt;Anders Hansen, a Swedish psychiatrist talks a lot about this disconnect between what evolution has prioritised during the last 100k years vs what we would &amp;ldquo;need&amp;rdquo; right now to thrive in this information and abundance society.&lt;/p&gt;
&lt;p&gt;If you have not read his book, &amp;ldquo;The Attention Fix&amp;rdquo; I strongly recommend that you do. It&amp;rsquo;s definitely on the short-list of most influential books I have read within the last few years. It has really allowed me to view things in a new light, which is I think the best a book can do.&lt;/p&gt;
</description>
      <source:markdown>I thought it was crazy to think about all the 8.2 billion people.

A even crazier thought is how many internet users there are, who in theory can talk to anyone else.

From an information point of view it&#39;s just a amazing amount of informationand potential available.

Of course, not everyone has something to say to every other user. But the fact that people who are interested in a subject can &#34;easily&#34; talk to so many likeminded people should allow for humanity to develop at a never-before seen pace.

Are we? I don&#39;t know. Feels like things are stagnating, partially because of bad incentives. Or just human nature not having caught up with things.

Anders Hansen, a Swedish psychiatrist talks a lot about this disconnect between what evolution has prioritised during the last 100k years vs what we would &#34;need&#34; right now to thrive in this information and abundance society.

If you have not read his book, &#34;The Attention Fix&#34; I strongly recommend that you do. It&#39;s definitely on the short-list of most influential books I have read within the last few years. It has really allowed me to view things in a new light, which is I think the best a book can do.
</source:markdown>
    </item>
    
    <item>
      <title></title>
      <link>https://blog.nyman.re/2025/08/16/there-are-a-lot-of.html</link>
      <pubDate>Sat, 16 Aug 2025 23:40:37 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/16/there-are-a-lot-of.html</guid>
      <description>&lt;p&gt;There are a lot of people on this planet.&lt;/p&gt;
&lt;p&gt;Our brains are not designed for numbers that big.&lt;/p&gt;
&lt;p&gt;I put together a little art project about how many people there are.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://nyman.re/everyone-as-a-pixel.html&#34;&gt;nyman.re/everyone-&amp;hellip;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Makes you feel small doesn&amp;rsquo;t it?&lt;/p&gt;
&lt;p&gt;And still you can make a difference. Magical&lt;/p&gt;
</description>
      <source:markdown>There are a lot of people on this planet.

Our brains are not designed for numbers that big.

I put together a little art project about how many people there are.

[nyman.re/everyone-...](https://nyman.re/everyone-as-a-pixel.html)

Makes you feel small doesn&#39;t it?

And still you can make a difference. Magical

</source:markdown>
    </item>
    
    <item>
      <title></title>
      <link>https://blog.nyman.re/2025/08/14/claude-code-gemini-code-codex.html</link>
      <pubDate>Thu, 14 Aug 2025 23:10:26 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/14/claude-code-gemini-code-codex.html</guid>
      <description>&lt;p&gt;Claude Code &amp;gt; Gemini Code &amp;gt; Codex&lt;/p&gt;
&lt;p&gt;Based solely on my gut feeling after having played with each one of them on some toy projects.&lt;/p&gt;
&lt;p&gt;One pet peeve I had with Codex was that it did not want to finish things, when asked to move stuff, if it thought it was too much it just left &lt;put rest of code here&gt; at the end of the file.&lt;/p&gt;
&lt;p&gt;Gemini Pro worked fine the little I tried it but I dislike on principle because it&amp;rsquo;s not possible to deactivate the history when it&amp;rsquo;s part of the Google Suite/Workspace/Apps. I&amp;rsquo;m actually not sure if that affects Gemini Code.&lt;/p&gt;
&lt;p&gt;Claude Code seems to be a good balance.&lt;/p&gt;
&lt;p&gt;At some point I will try to compare by giving them the same instructions on the same project and see what actually happens.&lt;/p&gt;
</description>
      <source:markdown>Claude Code &gt; Gemini Code &gt; Codex

Based solely on my gut feeling after having played with each one of them on some toy projects.

One pet peeve I had with Codex was that it did not want to finish things, when asked to move stuff, if it thought it was too much it just left &lt;put rest of code here&gt; at the end of the file.

Gemini Pro worked fine the little I tried it but I dislike on principle because it&#39;s not possible to deactivate the history when it&#39;s part of the Google Suite/Workspace/Apps. I&#39;m actually not sure if that affects Gemini Code. 

Claude Code seems to be a good balance.

At some point I will try to compare by giving them the same instructions on the same project and see what actually happens.

</source:markdown>
    </item>
    
    <item>
      <title>Putting your crypto you didn&#39;t know about to good use</title>
      <link>https://blog.nyman.re/2025/08/13/putting-your-crypto-you-didnt.html</link>
      <pubDate>Wed, 13 Aug 2025 22:42:27 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/13/putting-your-crypto-you-didnt.html</guid>
      <description>&lt;p&gt;Do you have an old Keybase account? Or do you know someone who has? If not, you can stop reading now. If you have, and you weren&amp;rsquo;t into the crypto stuff back in the days you probably have around 500 EUR / 600 USD lying around there in magic internet money.&lt;/p&gt;
&lt;p&gt;With the world on fire, those money is not doing any good locked into some internet wallet. So I&amp;rsquo;d recommend getting them out of there and spend them on either yourself, someone you care about, or something good like charity.&lt;/p&gt;
&lt;p&gt;It takes a bit of effort, as Keybase does not allow you to transfer them directly, but thanks to a &lt;a href=&#34;https://blog.yaelwrites.com/how-to-extract-stellar-lumens-from-keybase/&#34;&gt;great writeup from Yael&lt;/a&gt; you can do it.&lt;/p&gt;
&lt;p&gt;After you have imported them into LOBSTR or some other wallet, you can donate them directly to charity with &lt;a href=&#34;https://www.daffy.org&#34;&gt;www.daffy.org&lt;/a&gt; (is one of the few I found that accepts XLM) , &lt;a href=&#34;https://www.redcross.org.uk/get-involved/donate/ways-you-can-give/donate-in-crypto&#34;&gt;The British Red Cross&lt;/a&gt; accepts some other crypto.&lt;/p&gt;
&lt;p&gt;Or you can transfer them to an exchange to turn them into real money and donate that.&lt;/p&gt;
&lt;p&gt;Use a reputable exchange if so. Because crypto is mostly crime, you will have to do a lot of KYC (know your customer, sending your ID and such) so pick one you trust with your data. After a bit of research I used bitstamp, and hopefully that won&amp;rsquo;t turn out too badly. But do your own research and pick one suitable to your jurisdiction.&lt;/p&gt;
&lt;h2 id=&#34;blaugust&#34;&gt;blaugust&lt;/h2&gt;
&lt;p&gt;Another blaugust post, not much editing. I have a much longer draft, discussing the various charities which accept crypto and how to make the most out of your XLM. I might publish in the future, will link to it then if so.&lt;/p&gt;
</description>
      <source:markdown>Do you have an old Keybase account? Or do you know someone who has? If not, you can stop reading now. If you have, and you weren&#39;t into the crypto stuff back in the days you probably have around 500 EUR / 600 USD lying around there in magic internet money.

With the world on fire, those money is not doing any good locked into some internet wallet. So I&#39;d recommend getting them out of there and spend them on either yourself, someone you care about, or something good like charity.

It takes a bit of effort, as Keybase does not allow you to transfer them directly, but thanks to a [great writeup from Yael](https://blog.yaelwrites.com/how-to-extract-stellar-lumens-from-keybase/) you can do it.

After you have imported them into LOBSTR or some other wallet, you can donate them directly to charity with [www.daffy.org](https://www.daffy.org) (is one of the few I found that accepts XLM) , [The British Red Cross](https://www.redcross.org.uk/get-involved/donate/ways-you-can-give/donate-in-crypto) accepts some other crypto. 

Or you can transfer them to an exchange to turn them into real money and donate that.

Use a reputable exchange if so. Because crypto is mostly crime, you will have to do a lot of KYC (know your customer, sending your ID and such) so pick one you trust with your data. After a bit of research I used bitstamp, and hopefully that won&#39;t turn out too badly. But do your own research and pick one suitable to your jurisdiction.

## blaugust
Another blaugust post, not much editing. I have a much longer draft, discussing the various charities which accept crypto and how to make the most out of your XLM. I might publish in the future, will link to it then if so.
</source:markdown>
    </item>
    
    <item>
      <title>The one time my gabriel&#43;website@mydomain paid off.</title>
      <link>https://blog.nyman.re/2025/08/12/the-one-time-my-gabrielwebsitemydomain.html</link>
      <pubDate>Tue, 12 Aug 2025 23:35:12 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/12/the-one-time-my-gabrielwebsitemydomain.html</guid>
      <description>&lt;p&gt;I have for a long time been using the + format to create &amp;ldquo;unique&amp;rdquo; emails for companies. Nowadays I&amp;rsquo;ve levelled that up with using &amp;lt;word&amp;gt;@rnd.mydomain which is a wildcard for the domain. I&amp;rsquo;ll write a blog about why it&amp;rsquo;s better and how to do it sometime.&lt;/p&gt;
&lt;p&gt;Either way, this is a repost of an old twitter thread.&lt;/p&gt;
&lt;p&gt;Back in 2020, I was been repeatedly called by a bitcoin/CFD company who refused to accept a &amp;ldquo;I&amp;rsquo;m not interested&amp;rdquo;. Let&amp;rsquo;s call them Company MagicMoney.
I had my suspicions on where they got my number from and I got confirmation when I asked them to email me the inf, and they sent me a mail to gabriel+&lt;em&gt;somehwat-legit-trading-platform&lt;/em&gt;@mydomain.&lt;/p&gt;
&lt;p&gt;And as the original company is EU based I sent them a #GDPR request to see what they are doing with my data and maybe get them to stop giving it to shady trade platforms based in St. Vincent and the Grenadines.&lt;/p&gt;
&lt;p&gt;I did get a response with a what looks like a JSON dump which seemed to contain most relevant interactions I had with the site. And it was completed within 30 days so at least their compliance department was working.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/26224/2025/gdprexcerpt.jpg&#34; alt=&#34;&#34; title=&#34;gdprexcerpt.jpeg&#34; border=&#34;0&#34; width=&#34;902&#34; height=&#34;1132&#34; /&gt;
&lt;p&gt;But as for how the data ended up in St. Vincent and the Grenadines? They denied ever sharing or selling it.&lt;/p&gt;
&lt;p&gt;Which might be true or not, either way I just exercised my right to have my data deleted and called it a day.&lt;/p&gt;
&lt;h2 id=&#34;endnote&#34;&gt;Endnote&lt;/h2&gt;
&lt;p&gt;This happened in 2020, I don&amp;rsquo;t remember which companies were involved or why had signed up with &lt;em&gt;Somewhat-Legit-Trading-Company&lt;/em&gt;. Interestingly, after I asked them to delete all my info, the calling stopped. Coincidence? Maybe.&lt;/p&gt;
&lt;h3 id=&#34;blaugust&#34;&gt;Blaugust&lt;/h3&gt;
&lt;p&gt;Another blaugust post tagged as draft.&lt;/p&gt;
</description>
      <source:markdown>I have for a long time been using the + format to create &#34;unique&#34; emails for companies. Nowadays I&#39;ve levelled that up with using \&lt;word\&gt;@rnd.mydomain which is a wildcard for the domain. I&#39;ll write a blog about why it&#39;s better and how to do it sometime.

Either way, this is a repost of an old twitter thread.

Back in 2020, I was been repeatedly called by a bitcoin/CFD company who refused to accept a &#34;I&#39;m not interested&#34;. Let&#39;s call them Company MagicMoney.
I had my suspicions on where they got my number from and I got confirmation when I asked them to email me the inf, and they sent me a mail to gabriel+*somehwat-legit-trading-platform*@mydomain. 

And as the original company is EU based I sent them a #GDPR request to see what they are doing with my data and maybe get them to stop giving it to shady trade platforms based in St. Vincent and the Grenadines. 

I did get a response with a what looks like a JSON dump which seemed to contain most relevant interactions I had with the site. And it was completed within 30 days so at least their compliance department was working. 

&lt;img src=&#34;https://cdn.uploads.micro.blog/26224/2025/gdprexcerpt.jpg&#34; alt=&#34;&#34; title=&#34;gdprexcerpt.jpeg&#34; border=&#34;0&#34; width=&#34;902&#34; height=&#34;1132&#34; /&gt;

But as for how the data ended up in St. Vincent and the Grenadines? They denied ever sharing or selling it.

Which might be true or not, either way I just exercised my right to have my data deleted and called it a day. 

## Endnote
This happened in 2020, I don&#39;t remember which companies were involved or why had signed up with *Somewhat-Legit-Trading-Company*. Interestingly, after I asked them to delete all my info, the calling stopped. Coincidence? Maybe.

### Blaugust
Another blaugust post tagged as draft. 
</source:markdown>
    </item>
    
    <item>
      <title>Logcheck helper draft release</title>
      <link>https://blog.nyman.re/2025/08/11/logcheck-helper.html</link>
      <pubDate>Mon, 11 Aug 2025 22:35:32 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/11/logcheck-helper.html</guid>
      <description>&lt;p&gt;A few days ago I &lt;a href=&#34;https://blog.nyman.re/2025/08/05/logcheck-for-turris-omnia-and.html&#34;&gt;blogged about how great logcheck&lt;/a&gt; was. And towards the end I mentioned that writing rules was one of the more annoying parts.&lt;/p&gt;
&lt;p&gt;I have for a while been considering how that can be done easier, and while I don&amp;rsquo;t have my dream solution yet, I have spent a few evenings vibe-coding something that I believe will be helpful.&lt;/p&gt;
&lt;h2 id=&#34;logcheck-regex-helper&#34;&gt;Logcheck regex helper&lt;/h2&gt;
&lt;p&gt;You can try it out at &lt;a href=&#34;https://nyman.re/logcheck-helper/&#34;&gt;nyman.re/logcheck-helper/&lt;/a&gt; right now, or keep reading to learn more about the motivation and possible future development.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/26224/2025/regexhelper-screenshot.png&#34; alt=&#34;Screenshot from the Logcheck Regex Helper Webpage showing a sample regex&#34; title=&#34;regexhelper-screenshot.png&#34; border=&#34;0&#34; width=&#34;534&#34; height=&#34;600&#34; /&gt;
&lt;h2 id=&#34;why-is-writing-rules-for-logcheck-hard&#34;&gt;Why is writing rules for logcheck hard?&lt;/h2&gt;
&lt;p&gt;First, it uses &lt;code&gt;egrep&lt;/code&gt; (which is an alias for &lt;code&gt;grep -E&lt;/code&gt; nowadays), which uses POSIX Extended Regular Expressions. It&amp;rsquo;s in my experience not the most user friendly regex. Then when you have written one, testing it requires that you do something like &lt;code&gt;logcheck-test -r /etc/logcheck/ignore.d.server/custom -l /var/log/syslog|grep named&lt;/code&gt;, but you need to pick the right log and the right rule file.&lt;/p&gt;
&lt;p&gt;So what I usually end up doing is writing very coarse matches, which isn&amp;rsquo;t optimal.&lt;/p&gt;
&lt;h2 id=&#34;how-to-fix-this&#34;&gt;How to fix this&lt;/h2&gt;
&lt;p&gt;The main problem with logcheck is that it&amp;rsquo;s noisy, and in combination with writing new ignores being hard, it becomes noise very quickly and nobody reads them.
To allow the end user to cut down on the noise, it must really easy to do so. One idea I&amp;rsquo;ve had is that the alert emails would have a [ignore] button for each line (or all) which will create ignore similar log lines in the future. It would then optionally sync those ignores to all servers so we don&amp;rsquo;t need to repeat ourselves.&lt;/p&gt;
&lt;p&gt;But I am not there yet.&lt;/p&gt;
&lt;h2 id=&#34;what-is-logcheck-regex-helper&#34;&gt;What is Logcheck Regex Helper&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s a quick proof of concept that tries to help you write the regex. It explores my ideas around providing the user with a working regex, while still allowing them to customise it. In the current version, you can click on the patterns in the pattern-configurator to switch between the regex targeting any text, or that literal text.&lt;/p&gt;
&lt;p&gt;This should allow the user to quickly get a decent rule, that can be modified for their use case.&lt;/p&gt;
&lt;h2 id=&#34;why-vibe-coding&#34;&gt;Why vibe coding?&lt;/h2&gt;
&lt;p&gt;Because this is what it&amp;rsquo;s good at. Putting together a &amp;ldquo;simple&amp;rdquo; proof of concept and allowing for quick experimentation.&lt;/p&gt;
&lt;p&gt;And this is all client-side, so there is no risk that it would create a vulnerability.&lt;/p&gt;
&lt;p&gt;And what it produces is not critical in any way. If it produces dumb logcheck rules, you will hopefully spot it and worst case you probably just fail to ignore what you wanted ignored.&lt;/p&gt;
&lt;h2 id=&#34;but-the-poc-is-now-deployed-so-its-in-production&#34;&gt;But the PoC is now deployed?!? So it&amp;rsquo;s in production?!&lt;/h2&gt;
&lt;p&gt;Yes. But only because it&amp;rsquo;s all client side. So there is no risk that bad code could cause issues&amp;hellip; (for me).&lt;/p&gt;
&lt;p&gt;If this had a server side, I would either sandbox it really hard or not publish it. Probably both.&lt;/p&gt;
&lt;h2 id=&#34;will-you-open-source-it&#34;&gt;Will you open source it?&lt;/h2&gt;
&lt;p&gt;It is open-source&amp;hellip; like all client side web applications. It&amp;rsquo;s not even minified. But if someone asks nicely and would like to contribute or fork it, I will publish the repo. The license is 2-BSD so feel free to copy it.&lt;/p&gt;
&lt;h2 id=&#34;blaugust&#34;&gt;Blaugust&lt;/h2&gt;
&lt;p&gt;This is another blaugust post, no editing or spell-checking. Sorry.&lt;/p&gt;
</description>
      <source:markdown>A few days ago I [blogged about how great logcheck](https://blog.nyman.re/2025/08/05/logcheck-for-turris-omnia-and.html) was. And towards the end I mentioned that writing rules was one of the more annoying parts.

I have for a while been considering how that can be done easier, and while I don&#39;t have my dream solution yet, I have spent a few evenings vibe-coding something that I believe will be helpful.

## Logcheck regex helper
You can try it out at [nyman.re/logcheck-helper/](https://nyman.re/logcheck-helper/) right now, or keep reading to learn more about the motivation and possible future development.

&lt;img src=&#34;https://cdn.uploads.micro.blog/26224/2025/regexhelper-screenshot.png&#34; alt=&#34;Screenshot from the Logcheck Regex Helper Webpage showing a sample regex&#34; title=&#34;regexhelper-screenshot.png&#34; border=&#34;0&#34; width=&#34;534&#34; height=&#34;600&#34; /&gt;

## Why is writing rules for logcheck hard?
First, it uses `egrep` (which is an alias for `grep -E` nowadays), which uses POSIX Extended Regular Expressions. It&#39;s in my experience not the most user friendly regex. Then when you have written one, testing it requires that you do something like `logcheck-test -r /etc/logcheck/ignore.d.server/custom -l /var/log/syslog|grep named`, but you need to pick the right log and the right rule file.

So what I usually end up doing is writing very coarse matches, which isn&#39;t optimal.

## How to fix this
The main problem with logcheck is that it&#39;s noisy, and in combination with writing new ignores being hard, it becomes noise very quickly and nobody reads them.
To allow the end user to cut down on the noise, it must really easy to do so. One idea I&#39;ve had is that the alert emails would have a [ignore] button for each line (or all) which will create ignore similar log lines in the future. It would then optionally sync those ignores to all servers so we don&#39;t need to repeat ourselves. 

But I am not there yet.

## What is Logcheck Regex Helper
It&#39;s a quick proof of concept that tries to help you write the regex. It explores my ideas around providing the user with a working regex, while still allowing them to customise it. In the current version, you can click on the patterns in the pattern-configurator to switch between the regex targeting any text, or that literal text.

This should allow the user to quickly get a decent rule, that can be modified for their use case.

## Why vibe coding?
Because this is what it&#39;s good at. Putting together a &#34;simple&#34; proof of concept and allowing for quick experimentation.

And this is all client-side, so there is no risk that it would create a vulnerability.

And what it produces is not critical in any way. If it produces dumb logcheck rules, you will hopefully spot it and worst case you probably just fail to ignore what you wanted ignored.

## But the PoC is now deployed?!? So it&#39;s in production?!
Yes. But only because it&#39;s all client side. So there is no risk that bad code could cause issues... (for me).

If this had a server side, I would either sandbox it really hard or not publish it. Probably both.

## Will you open source it?
It is open-source... like all client side web applications. It&#39;s not even minified. But if someone asks nicely and would like to contribute or fork it, I will publish the repo. The license is 2-BSD so feel free to copy it.

## Blaugust
This is another blaugust post, no editing or spell-checking. Sorry.
</source:markdown>
    </item>
    
    <item>
      <title></title>
      <link>https://blog.nyman.re/2025/08/10/vibe-coding-is-a-slot.html</link>
      <pubDate>Sun, 10 Aug 2025 23:56:25 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/10/vibe-coding-is-a-slot.html</guid>
      <description>&lt;p&gt;Vibe coding is a slot machine. I agree.&lt;/p&gt;
&lt;p&gt;You pull the lever and out comes code. Most of the time it&amp;rsquo;s close but not right, so you pull again.&lt;/p&gt;
&lt;p&gt;Meanwhile you watch the &lt;a href=&#34;https://github.com/google-gemini/gemini-cli/blob/main/packages/cli/src/ui/hooks/usePhraseCycler.ts&#34;&gt;funny little loading messages&lt;/a&gt; they added to give a bit of extra dopamine kick.&lt;/p&gt;
&lt;p&gt;But it&amp;rsquo;s the most useful slot machine I&amp;rsquo;ve ever seen.&lt;/p&gt;
</description>
      <source:markdown>Vibe coding is a slot machine. I agree.

You pull the lever and out comes code. Most of the time it&#39;s close but not right, so you pull again.

Meanwhile you watch the [funny little loading messages](https://github.com/google-gemini/gemini-cli/blob/main/packages/cli/src/ui/hooks/usePhraseCycler.ts) they added to give a bit of extra dopamine kick.

But it&#39;s the most useful slot machine I&#39;ve ever seen. 
</source:markdown>
    </item>
    
    <item>
      <title></title>
      <link>https://blog.nyman.re/2025/08/09/why-im-not-worried-about.html</link>
      <pubDate>Sat, 09 Aug 2025 22:17:47 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/09/why-im-not-worried-about.html</guid>
      <description>&lt;p&gt;Why I&amp;rsquo;m not worried about my job part.&lt;/p&gt;
&lt;p&gt;Evidence 17: Google forcibly rolling Gemini everywhere before anyone has had a chance to actually consider the security implications.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=7Nasf-st1KQ&#34;&gt;www.youtube.com/watch&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;From &lt;a href=&#34;https://news.risky.biz/risky-bulletin-cisa-tells-federal-agencies-to-mitigate-on-prem-to-cloud-exchange-attack/&#34;&gt;the risky biz newsletter&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;#blaugust&lt;/p&gt;
</description>
      <source:markdown>Why I&#39;m not worried about my job part.

Evidence 17: Google forcibly rolling Gemini everywhere before anyone has had a chance to actually consider the security implications.

[www.youtube.com/watch](https://www.youtube.com/watch?v=7Nasf-st1KQ)

From [the risky biz newsletter](https://news.risky.biz/risky-bulletin-cisa-tells-federal-agencies-to-mitigate-on-prem-to-cloud-exchange-attack/)

#blaugust
</source:markdown>
    </item>
    
    <item>
      <title>We will need a gym for our brain</title>
      <link>https://blog.nyman.re/2025/08/08/we-will-need-a-gym.html</link>
      <pubDate>Fri, 08 Aug 2025 23:15:39 +0300</pubDate>
      
      <guid>http://gnyman.micro.blog/2025/08/08/we-will-need-a-gym.html</guid>
      <description>&lt;p&gt;This is related to &lt;a href=&#34;https://blog.nyman.re/2025/08/06/vibe-coding-is-great-until.html&#34;&gt;my previous post&lt;/a&gt; on LLM&amp;rsquo;s. Feel free to skip it if had too much LLM.&lt;/p&gt;
&lt;p&gt;There have been several studies on how LLM&amp;rsquo;s seem to make us dumber. And there is probably truth in that. It&amp;rsquo;s quite logical if we look at the biology. The brain requires more energy when it&amp;rsquo;s working, and probably even more when forming new memories.&lt;/p&gt;
&lt;p&gt;So if there is an easier way, it will prefer that way, as for most of humanity&amp;rsquo;s existence wasted energy was not good for survival. And biologically, like your muscles will become smaller unless you exercise them, the brain probably does the same.&lt;/p&gt;
&lt;p&gt;So I think &amp;ldquo;brain exercise&amp;rdquo; will become more important as LLMs become more useful, just like intentional exercise like going to the gym is an important part of our lifestyle because of powered transport like cars.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t know what will it look like. But I&amp;rsquo;m quite sure it won&amp;rsquo;t be like the &amp;ldquo;brain training&amp;rdquo; applications. They claim to make you smarter, but based on what I&amp;rsquo;ve understood they are a bit too limited. From what I&amp;rsquo;ve read, you become better at doing their brain games but it does not translate well effectively to becoming better other things.&lt;/p&gt;
&lt;p&gt;So what kind of brain gym should you do? I guess whatever you want as long as it&amp;rsquo;s closely related to what you want to train and requires thinking, but intentionally leave the LLM out of it.&lt;/p&gt;
&lt;p&gt;Although you might actually want the LLM before and after, because an important part of becoming better at something is that the difficulty is right, so it challenges you. Creating customised challenges, and evaluating them afterwards is probably something a LLM is good at.&lt;/p&gt;
&lt;h3 id=&#34;blaugust&#34;&gt;blaugust&lt;/h3&gt;
&lt;p&gt;This is another blaugust post, very little editing so it&amp;rsquo;s marked as draft.&lt;/p&gt;
</description>
      <source:markdown>This is related to &lt;a href=&#34;https://blog.nyman.re/2025/08/06/vibe-coding-is-great-until.html&#34;&gt;my previous post&lt;/a&gt; on LLM&#39;s. Feel free to skip it if had too much LLM.

There have been several studies on how LLM&#39;s seem to make us dumber. And there is probably truth in that. It&#39;s quite logical if we look at the biology. The brain requires more energy when it&#39;s working, and probably even more when forming new memories.

So if there is an easier way, it will prefer that way, as for most of humanity&#39;s existence wasted energy was not good for survival. And biologically, like your muscles will become smaller unless you exercise them, the brain probably does the same.

So I think &#34;brain exercise&#34; will become more important as LLMs become more useful, just like intentional exercise like going to the gym is an important part of our lifestyle because of powered transport like cars.

I don&#39;t know what will it look like. But I&#39;m quite sure it won&#39;t be like the &#34;brain training&#34; applications. They claim to make you smarter, but based on what I&#39;ve understood they are a bit too limited. From what I&#39;ve read, you become better at doing their brain games but it does not translate well effectively to becoming better other things.

So what kind of brain gym should you do? I guess whatever you want as long as it&#39;s closely related to what you want to train and requires thinking, but intentionally leave the LLM out of it. 

Although you might actually want the LLM before and after, because an important part of becoming better at something is that the difficulty is right, so it challenges you. Creating customised challenges, and evaluating them afterwards is probably something a LLM is good at. 

### blaugust
This is another blaugust post, very little editing so it&#39;s marked as draft.
</source:markdown>
    </item>
    
  </channel>
</rss>
