First Look: Apple’s NEURLFilter API

At WWDC 2025, Apple introduced an interesting new API, NEURLFilter, to respond to a key challenge we’ve talked about previously: the inherent conflict between privacy and security when trying to protect users against web threats. That conflict means that security filtering code usually cannot see a browser’s (app’s) fetched URLs to compare them against available threat intelligence and block malicious fetches. By supplying URLs directly to security software (a great idea!), the conflict between security and privacy need not be so stark.

Their presentation about the tech provides a nice explanation of how the API is designed to ensure that the filter can block malicious URLs without visibility into either the URL or where (e.g. IP) the client is coming from.

At a high-level, the design is generally similar to that of Google SafeBrowsing or Defender’s Network Protection — a clientside bloom filter of “known bad” URLs is consulted to see whether the URL being loaded is known bad. If the filter misses, then the fetch is immediately allowed (bloom filters never false positive). If the bloom filter indicates a hit, then a request to an online reputation service is made to get a final verdict.

Privacy Rules

Now, here’s where the details start to vary from other implementations: Apple’s API sends the reputation request to an Oblivious HTTP relay to “hide” the client’s network location from the filtering vendor. Homomorphic encryption is used to perform a “Private Information Retrieval” to determine whether the URL is in the service-side block database without the service actually being able to “see” that URL.

Filtering requests are sent automatically by WebKit and Apple’s native URLSession API. Browsers that are not built on Apple’s HTTPS fetchers can participate by calling an explicit API:

Neat, right? Well, yes, it’s very cool.

Is it perfect for use in every product? No.

Limitations

Inherent in the system design is the fact that Apple has baked its security/privacy tradeoffs into the design without allowing overrides. Here are some limitations that may cause filtering vendors trouble:

  1. Reputation checks can no longer discover new URLs that might represent unblocked threats, or use lookups to prioritize security rescans for high-volume URLs.
  2. There does not seem to be any mechanism to control which components of a URL are evaluated, such that things like rollups can be controlled.
  3. Reputation services cannot have rules that evaluate only certain portions of a URL (e.g. if an campaign is run across many domains with a specific pattern in the path or query).
  4. There does not appear to be any mechanism to submit additional contextual information (e.g. redirect URLs, IP addresses) nor any way to programmatically weight it on the service side (to provide resiliency against cloaking).
  5. There does not appear to be any mechanism which would allow for non-Internet operation (e.g. within a sovereign cloud), or to ensure that reputation traffic flows through only a specific geography.
  6. There’s no mechanism for the service to return a non-binary verdict (e.g. “Warn and allow override” or “Run aggressive client heuristics”).
  7. When a block occurs in an Apple client, there is no mechanism to allow the extension to participate in a feedback experience (e.g. “Report false positive to service”).
  8. There’s no apparent mechanism to determine which client device has performed a reputation check (allowing a Security Operations Center to investigate any potential compromise).
  9. The fastest-allowed Bloom filter update latency is 45 minutes.

Apple’s position is that “Privacy is a fundamental human right” which is an absolutely noble position to hold. However, the counterpoint is that the most fundamental violation of a computer user’s privacy occurs upon phishing theft of their passwords or deployment of malware that steals their local files. Engineering is all about tradeoffs, and in this API, Apple controls the tradeoffs.

Verdict?

Do the limitations above mean that Apple’s API is “bad”? Absolutely not. It’s a brilliantly-designed, powerful privacy-preserving API for a great many use-cases. If I were installing, say, Parental Controls software on my child’s Mac, it’s absolutely the API that I would want to see used by the vendor.

You can learn more about Apple’s new API in the NEURLFilterManager documentation.

-Eric

PS: I’ve asked the Chromium folks whether they plan to call this API.

Web Category Filtering

Since the first days of the web, users and administrators have sought to control the flow of information from the Internet to the local device. There are many different ways to implement internet filters, and numerous goals that organizations may want to achieve:

Today’s post explores the last of these: blocking content based on category.

The Customer Goal

The customer goal is generally a straightforward one: Put the administrator in control of what sorts of content may be downloaded and viewed on a device. This is often intended as an enforcement mechanism for an organization’s Acceptable Use Policy (AUP).

An AUP often defines what sorts of content a user is permitted to interact with. For example, a school may forbid students from viewing pornography and any sites related to alcohol, tobacco, gambling, firearms, hacking, or other criminal activity. Similarly, a company may want to forbid their employees from spending time on data and social media sites, or from using legitimate-but-unsanctioned tools for sharing files, conducting meetings, or interacting with artificial intelligence.

Mundane Impossibility

The simplicity of the goal belies the impossibility of achieving it. On today’s web, category filtering is inherently impossible to perfect for several reasons:

  1. New sites arrive all day, every day.
  2. The content served by any site can change at any time.
  3. There are an infinite number of categories for content (and no true standard taxonomy).
  4. Decisions of a site’s category are inherently subjective.
  5. Many sites host content across multiple categories, and some sites host content from almost every category.
  6. Self-labelling schemes like ICRA and PICS (Platform for Internet Content Selection), whereby a site can declare its own category, have all failed to be adopted.

As an engineer, while it would be nice to work on only tractable problems, in life there are many intractable problems for which customers are willing to buy imperfect best-effort solutions.

Web content categorization is one of these, and because sites and categories change constantly, it’s typically the case that content filtering is sold on a subscription basis rather than as a one-time charge. Most of today’s companies love recurring revenue streams.

So, given that customers have a need, and software can help, how do we achieve that?

Filtering Approaches

The first challenge is figuring out how and where to block content. There are many approaches; Microsoft’s various Web Content Filtering products and features demonstrate three of them:

Each implementation approach has its plusses and minuses, from:

  1. Supported browsers: does it work in any browser? Only a small list? Only a specific one?
  2. Performance: Does it slow down browsing? Because the product may categorize billions of URLs, it’s usually not possible to store the map on the client device.
  3. User-experience: What kind of block notice can be shown? Does it appear in context?
  4. Capabilities: Does it block only on navigating a frame (e.g. HTML), or can it block any sub-resources (images, videos, downloads, etc)? Are blocks targeted to the current user, or to the entire device?

Categorization

After choosing a filtering approach, the developer must then choose a source of categorization information. Companies that are already constantly crawling and monitoring the web (e.g. to build search engines like Bing or Google) might perform categorization themselves, but most vendors acquire data from a classification vendor like NetStar or Cyren that specializes in categorization.

Exposing the classification vendor’s entire taxonomy might be problematic though– if you bind your product offering too tightly to a 3rd-party classification, any taxonomy changes made by the classification vendor could become a breaking change for your product and its customers. So, it’s tempting to go the other way, and ask your customers what categories they expect, then map any of the classification vendor’s taxonomy onto the customer-visible categories.

This is the approach taken by Microsoft Defender WCF, for example, but it can lead to surprises. For example, Defender WCF classifies archive.org in the Illegal Software category, because that’s where our data vendor’s Remote Proxies category is mapped. But to the browser user, that opaque choice might be very confusing — while archive.org almost certainly contains illegal content (it’s effectively a time-delayed proxy for the entire web), that is not the category a normal person would first think of when asked about the site.

Ultimately, an enterprise that implements Web Content Filtering must expect that there will be categorizations with which they disagree, or sites whose categories they agree with but wish to allow anyway (e.g. because they run ads on a particular social networking site, for instance). Administrators should define a process by which users can request exemptions or reclassifications, and then the admins evaluate whether the request is reasonable. Within Defender, an ALLOW Custom Network indicator will override any WCF category blocks of a site.

Aside: Performance

If your categorization approach requires making a web-service request to look up a content category, you typically want to do so in parallel with the request for the content to improve performance. However, what happens if the resource comes back before the category information?

Showing the to-be-blocked content (e.g. a pornographic website) for even a few seconds might be unacceptable. To address that concern for Defender’s WCF, Edge currently offers the following flag on the about:flags page:

Aside: Sub-resources

It’s natural to assume that checking the category of all sub-resources would be superior to only checking the category of page/frame navigations: after all, it’s easy to imagine circumventing, say, an adult content filter by putting up a simple webpage at some innocuous location with a ton of <video> elements that point directly to pornographic video content. A filter that blocks only top-level navigations will not block the videos.

However, the opposite problem can also occur. For example, an IT department recently tried to block a wide swath of Generative AI sites to ensure that company data was not shared with an unapproved vendor. However, the company also outsourced fulfillment of its benefits program to an approved 3rd party vendor. That Benefits website relied upon a Help Chat Bot powered by a company that had recently pivoted into generative AI. Employees visiting the Benefits website were now seeing block notifications from Web Content Filtering due to the .js file backing the Help Chat Bot. Employees were naturally confused — they were using the site that HR told them to use, and got block notifications suggesting that they shouldn’t be using AI. Oops.

Aside: New Sites

Generally, web content filtering is not considered a security feature, even if it potentially reduces an organization’s attack surface by reducing the number of sites a user may visit. Of particular interest is a New Sites category — if an organization blocks users from accessing all sites that are newer than, say, 30 days, and which have not yet been categorized into another category, not only do they reduce the chance of a new site evading a block policy (e.g. a new pornographic site that hasn’t yet been classified by the vendor), this also provides a form of protection from a spear-phishing attack.

Unfortunately, providing a robust implementation of a New Sites category isn’t as easy as it sounds: for a data vendor to classify a site, they have to know its domain name exists. Depending upon the vendors data collection practices, that discovery might take quite a bit of time. That’s because of how the Internet is designed: there’s no “announcement” when a new domain goes online. Instead, a DNS server simply gets a new record binding the site’s hostname to its hosting IP address, and that new record is returned only if a client asks for it.

Simply treating all unclassified sites as “new” has its own problems (what if the site is on a company’s intranet and the data vendor will never be able to access it?). Instead, vendors might learn about new sites by monitoring Certificate Transparency logs, crawling web content that links to the new domain, watching DNS resolutions from broadly deployed client monitoring software (“Passive DNS”), or by integrating with browsers (e.g. as a browser extension) to discover new sites as users navigate to them. After a domain is discovered, the vendor can attempt to load it and use its classification engine to determine the categories to which the site should belong.

-Eric

PS: Over on BlueSky, Nathan McNulty compares Defender WCF’s list of categories with GSA’s list.

Fiddler in 2025

The Fiddler Web Debugger is now old enough to drink, but I still use it pretty much every day. Fiddler hasn’t aged entirely gracefully as platforms and standards have changed over the decades, but the tool is extensible enough that some of the shortcomings can be fixed by extensions and configuration changes.

Last year, I looked back at a few of the mistakes and wins I had in developing Fiddler, and in this post, I explore how I’ve configured Fiddler to maximize my productivity today.

Powerup with FiddlerScript & Extensions

Add a SingleBrowserMode button to Fiddler’s toolbar

By default, Fiddler registers itself as the system proxy and almost all applications on the system will immediately begin sending their traffic through Fiddler. While this can be useful, it often results in a huge amount of uninteresting “noise”, particularly for web developers hoping to see only browser traffic. Fiddler’s rich filtering system can hide traffic based on myriad criteria, but for performance and robustness reasons, it’s best not to have unwanted traffic going through Fiddler at all.

The easiest way to achieve that is to simply not register as the system proxy and instead just launch a single browser instance whose proxy settings are configured to point at Fiddler’s endpoint.

Adding a button to Fiddler’s toolbar to achieve this requires only a simple block of FiddlerScript:

// Rules > Customize Rules, place this just inside the HANDLERS class…
// Add a button to Fiddler's UI for "Single Browser Mode", where only one browser window will
// send its traffic to Fiddler.
public static BindUIButton("SingleBrowserMode \uD83D\uDC40")
function LaunchSingleInstance() {
// Tell the system we're not the proxy anymore
FiddlerApplication.UI.actDetachProxy();
// Launch a single browser instance pointed directly at Fiddler.
System.Diagnostics.Process.Start('msedge.exe',
'–user-data-dir="%temp%\\throwaway" –no-first-run –proxy-server=127.0.0.1:' + CONFIG.ListenPort.ToString() + " about:blank");
}
view raw Single.js hosted with ❤ by GitHub
A new button appears! #Awesomesauce

This button is probably the single most-valuable change I made to my copy of Fiddler in years, and I’m honestly a bit sick that I never thought to include this decades ago.

Disable ZSTD

ZStandard is a very fast lossless compression algorithm that has seen increasing adoption over the last few years, joining deflate/gzip and brotli. Unfortunately, Telerik has not added support for Zstd compression to Fiddler Classic. While it would be possible to plumb support in via an extension, the simpler approach is to simply change outbound requests so that they don’t ask for this format from web servers.

Doing so is simple: just rewrite the Accept-Encoding request header:

// Add just inside here:
// static function OnBeforeRequest(oSession: Session) {
// Don't request zstd content-encoding because Telerik didn't bother adding support.
if (oSession.RequestHeaders.ExistsAndContains("Accept-Encoding", "zstd")) {
oSession.RequestHeaders["Accept-Encoding"] = oSession.RequestHeaders["Accept-Encoding"].Replace(", zstd", "");
}
view raw no Zstd.js hosted with ❤ by GitHub

Integrate with VirusTotal

Since moving to the Microsoft Defender team, I spend a lot more time looking at malicious files. You can integrate Fiddler into VirusTotal to learn more about any of the binaries it captures.

public static ContextAction("Show Hashes")
function doHash(arrSess: Session[])
{
for (var i: int=0; i<arrSess.Length; i++)
{
FiddlerObject.alert(
"_MD5_\n"+arrSess[i].GetResponseBodyHash("md5") + "\n\n" +
"_SHA1_\n"+arrSess[i].GetResponseBodyHash("sha1") + "\n\n" +
"_SHA256_\n"+arrSess[i].GetResponseBodyHash("sha256") + "\n"
);
}
}
ContextAction("VirusTotal")
public static
function doVTCheck(arrSess: Session[])
{
for (var i: int=0; i<arrSess.Length; i++)
{
var oS = arrSess[i];
if (oS.bHasResponse)
{
Utilities.LaunchHyperlink(String.Format(
"https://www.virustotal.com/en/file/{0}/analysis/",
oS.GetResponseBodyHash("sha256").Replace("-","")));
}
}
}
view raw HashSample.js hosted with ❤ by GitHub

Beyond looking at hashes, I also spend far more time looking at malicious sites and binaries, many of which embed malicious content in base64 encoding. Fiddler’s TextWizard (Ctrl+E) offers a convenient way to transform Base64’d text back to the original bytes, and the Web Session List’s context menu’s “Copy > Response DataURI” allows you to easily base64 encode any data.

Add the NetLog Importer

If your goal isn’t to modify traffic with Fiddler, it’s often best not to have Fiddler capture browser traffic at all. Instead, direct your Chromium-based browser to log its the traffic into a NetLog.json file which you can later import to Fiddler to analyze using the Fiddler NetLog Importer extension.

Learn about using Fiddler to analyze NetLogs.

…And More…

There are a zillion other useful little scripts you might add to Fiddler for your own needs. If you look through the last ten years of my GitHub Gists you might find some inspiration.

Adjust Settings

Configure modern TLS settings

Inside Tools > Fiddler Options > HTTPS, make it look like this:

Use Visual Studio Code as the Diff Tool

If you prefer VSCode to Windiff, type about:config in the QuickExec box below the Web Sessions list to open Fiddler’s Preferences editor.

Add/update the fiddler.config.path.differ entry to point to the file path to your VSCode instance.

Set the fiddler.differ.params value to --diff "{0}" "{1}"

Miscellaneous

  • On the road and don’t have access to Fiddler? You can quickly explore a Fiddler SAZ file using a trivial web-based tool.
  • Developers can use Fiddler’s frontend as the UI for their own bespoke tools and processes. For example, I didn’t want to build a whole tampering UI for the Native Messaging Meddler, so I instead use Fiddler as the front-end.
  • Malware-hunter Jรฉrรดme Segura has put together a package of Fiddler customizations designed for hunting for malicious web traffic.
  • More? Do you have any great suggestions for how you’ve tailored Fiddler to your modern workflows? Send me a comment!

Attack Techniques: Fake Literally Everything! (Escrow Scam)

The team recently got a false-negative report on the SmartScreen phishing filter complaining that we fail to block firstline-trucking.com. I passed it along to our graders but then took a closer look myself. I figured that maybe the legit site was probably at a very similar domain name, e.g. firstlinetrucking.com or something, but no such site exists.

Curious.

Simple Investigation Techniques

I popped open the Netcraft Extension and immediately noticed a few things. First, the site is a new site. Suspicious, since they claim to have been around since 2002. Next, the site is apparently hosted in the UK, although they brag about being “Strategically located at the U.S.-Canada border.” Sus... and just above that, they supply an address in Texas. Sus.

Let’s take a look at that address in Google Maps. Hmm. A non-descript warehouse with no signage. Sus.

Well, let’s see what else we have. Let’s go to the “About Us” page and see who claims to be employed here. Right-click the CEO’s picture and choose “Copy image link.”

Paste that URL into TinEye to see where else that picture appears on the web. Ah, it’s from a stock photo site. Very sus.

Investigating the other employee photos and customer pictures from the “Customer testimonials” section reveals that most of them are also from stock photo sites. The unfortunately-named “Marry Hoe” has her picture on several other “About us” pages — it looks like she probably came with the template. Her profile page is all Lorem Ipsum placeholder text.

I was surprised that one of the biggest photos on the site didn’t show up in TinEye at all. Then I looked at the Developer Tools and noticed that the secret is revealed by the image’s filename — ai-generated-business-woman-portrait. Ah, that’ll do it.

I tried searching for the phone number atop the site ((956) 253-7799) but there were basically no hits on Google. This is both very sus and very surprising, because often Googling for a phone number will turn up many complaints about scams run from that number.

Moar Scams!

Hmm…. what about all of those blog posts on the site. They’re not all lorem ipsum text. Hrm… but they do reference other companies. Maybe these scammers just lifted the text from some legit company? It seems plausible that “New England Auto Shipping” is probably a legit company they stole this from. Let’s copy this text and paste it into Google:

I didn’t find the source (likely neautoshipping.com, an earlier version of the scam from October 2024), but I did find another live copy of the attack, hosted on a similar domain:

This version is hosted at firstline-vehicle.com with the phone number (908-505-5378) and an address in New Jersey. They’ve literally been copy/pasting their scam around!

Netcraft reports that it’s first seen next month ๐Ÿ™ƒ. Good thing I’ve got my time machine up and running!

The page title of this scam site doesn’t match the scammers though. Hmm… What happens if I look for “Bergen Auto Logistics” then?

Another scam site, bergen-autotrans.com, this one registered this month and CEO’d by a Stock Photo woman:

There are some more interesting photos here, including some that are less obviously faked:

It looks like there was an earlier version of this site in November 2024 at bergenautotrans.com that is now offline:

Searching around, we see that there’s also currently a legit business in New York named “Bergen Auto” whose name and reputation these scammers may have been trying to coast off of. And now some of the pieces are starting to make more sense — Bergen New York is on the US/Canada border.

Searching for the string "Your car does not need be running in order to be shipped" turns up yet more copies of the scam, including britt-trucking.net with phone number (602) 399-7327:

Another random Stock Photo CEO is here, and our same General Manager now has a new name:

…and hey, look, it’s our old friends, now with a different logo on their shirts!

Interestingly, if you zoom in on the photo, you see that the name and logo don’t even match the scam site. The company logo and filename contain Sunni-Transportation, which was also found in the filename of Marry Hoe on the first site we looked at.

The same "Your car does not need be running in order to be shipped" string was also found on two now-offline sites, unitedauto-transport.com, and unitedautotrans.net.

Not a Phish, but definitely Fishy

I went back to our original complainant and asked for clarification — this site doesn’t seem to be pretending to be the site of any other company, but instead appears to be just entirely manufactured from AI and stock photos.

He explained that the attackers troll Craigslist[1] looking for folks buying used cars. They put up some fake listings, and then act as if the (fake) seller has chosen them as an escrow provider. After a bunch of paperwork, the victim buyer wires the attacker thousands of dollars for the nonexistent car. The attackers immediately send a fake tracking number that goes to an order tracking page that’s never updated. They’re abusing people who are risk-averse enough to seek out an escrow company to protect a big transaction, but who not able to validate the bonafides of that “escrow company”… aka, smart humans. (Having bought houses thrice, I can say that validating the legitimacy of an escrow company is a very difficult task). Escrow scams like this one are only one of several popular attacks — this guide and this one describe several scams and how to avoid them.

The Better Business Bureau had a writeup of vehicle escrow scams way back in 2020, and the FTC a year before. Reddit even has an automatic bot to explain the scam. In 2021, an Ohio man was sentenced to 14 years in prison for stealing over $10M via this sort of scam.

Unfortunately, creating a fake business almost entirely in pixels is a simple scam, and one that’s not trivial to protect against. In cases where no existing business’ reputation is being abused, there’s no organization that’s particularly incentivized to do the work to get the bad guys taken down. Phishing protection features like SafeBrowsing and SmartScreen are not designed to protect against “business practices scams.”

The very same things that make online businesses so easy to start — low overhead, no real-estate, templates and AIs can do the majority of the work — make it easy to invent fake businesses that only exist in the minds of their victims. After the scammers get found out, the sites disappear and the crooks behind them simply fade away.

I advised the reporter to report the fraud to the FTC, the Internet Crime Complaint Center, and also to Netcraft, who do maintain feeds of scam sites of all types, not just phishing/malware.

Stay safe out there!

Update: As of September 12th, 2025, a new version of the escrow scam site is live:

https://fl-trans.com/

They’re using the same mix of stock photos and slightly edited media as the earlier versions:


This one appears to be hosted in Brazil:

-Eric

PS: Holy cow. https://escrow-fraud.com/search.php

Looking through here, most of the sites are dead, but not all. Some have been live for years!


[1] In college, a friend fell victim to a different scam on Craigslist, the overpayment scam. They’d rented a 3 bedroom apartment and needed a 3rd roommate. They were contacted by an “international student” who needed a room and sent my friends a check $500 dollars larger than requested. “Oops, would you mind wiring back that extra? I really need it right now!” the scammer begged. My kind friends wired back the “overpayment” amount, and a few days later were heartbroken to discover that the original check had, of course, not actually cleared. They were out the $500, a huge sum for two broke young college students.

This same overpayment scam is used in fake car sales too.

Vibe-coding for security

Recently, there’s been a surge in the popularity of trojan clipboard attacks whereby the attacker convinces the user to carry their attack payload across a security boundary and compromise the device.

Meanwhile, AI hype is all the rage. I recent had a bad experience in what I thought was a simple AI task (draw a map with pushpins in certain cities):

The generated map with wildly incorrect city locations

… but I was curious to see what AI would say if I pretended to be the target of a trojan clipboard attack. I was pleased to discover that the two AIs I tried both gave solid security advice for situation:

ChatGPT and Gemini both understood the attack and the risk

A few days later, the term “vibe-coding” crossed my feed and I groaned a bit when I learned what it means… Just describe what you want to the AI and it’ll build your app for you. And yet. That’s kinda exactly how I make a living as a PM: I describe what I want an app to do, and wait for someone else (ideally, our dev team) to build it. I skimmed a few articles about vibe coding and then moved on with my day. I don’t have a lot of time to set up new workflows, install new devtools, subscribe to code-specific AI models, and so forth.

Back to the day job.

Talking to some security researchers looking into the current wave of trojan clipboard attacks, I brainstormed some possible mitigations. We could try to make input surfaces more clear about risk:

… but as I noted in my old blog post, we could be even smarter, detecting when the content of a paste came from a browser (akin to the “Mark of the Web” on downloads) and provide the user with a context specific warning.

In fact, I realized, we don’t even need to change any of the apps. Years ago, I updated SlickRun to flash anytime the system clipboard’s content changes as a simple user-experience improvement. A simple security tool could do the same thing– watch for clipboard changes, see if the content came from the browser, and then warn the user if it was dangerous.

In the old days, I’d’ve probably spent an evening or two building such an app, but life is busier now, and my C++ skills are super rusty.

But… what if I vibe-coded it? Hmm. Would it work, or would it fail as spectacularly as it did on my map task?

Vibe-coding ClipShield

I popped open Google Gemini (Flash 2.0) and told directed it:

> Write me a trivial C++ app that calls AddClipboardFormatListener and on each WMClipboardUpdate call it scans the text on the clipboard for a string of my choice. If it's found, a MessageBox is shown and the clipboard text is cleared.

In about 15 seconds, it had emitted an entire C++ source file. I pasted it into Visual Studio and tried to compile it, expecting a huge pile of mistakes.

Sure enough, VS complained that there was no WinMain function. Gemini had named its function main(). I wonder if it could fix it itself?

> Please change the entry point from main to WinMain

The new code compiled and worked perfectly. Neat! I wonder how well it would do with making bigger changes to the code? Improvements occurred to me in rapid succession:

> To the WM_CLIPBOARDUPDATE code, please also check if the clipboard contains a format named "Chromium internal source URL". 

> Update the code so instead of a single searchString we search for any of a set of strings.

> please make the string search case-insensitive

> When blocking, please also emit the clipboard string in the alert, and send it to the debug console via OutputDebugString

In each case, the resulting code was pretty much spot on, although I took the opportunity to tweak some blocks manually for improved performance. Importantly, however, I wasn’t wasting any time on the usual C++ annoyances, string manipulations and conversions, argument passing conventions, et cetera. I was just… vibing.

There was a compiler warning from Visual Studio in the log. I wonder if it could fix that? I just pasted the error in with no further instruction:

> Inconsistent annotation for 'WinMain': this instance has no annotations. See c:\program files (x86)\windows kits\10\include\10.0.26100.0\um\winbase.h(1060). 

Gemini explained what the warning meant and exactly how to fix it. Hmm… What else?

> Is there a way to show the message box on a different thread so it does not block further progress?

Gemini refactored the code to show the alert in a different thread. Wait, is that even legal?

> In Windows API, is it legal to call MessageBox on another thread?

Gemini explained the principles around the UI thread and why showing a simple MessageBox was okay.

> Can you use a mutex to ensure single-instance behavior?

Done. I had to shift the code around a bit (I didn’t want errors to be fatal), but it was trivial.

Hmm…. What else. Ooh… What if I actually got real antivirus into the mix? I could call AMSI with the contents of the clipboard to let Defender or the system antivirus scan the content and give a verdict on whether it’s dangerous.

> Can you add code to call AMSI with the text from the clipboard?

It generated the code instantly. Amazing. Oops, it’s not quite right.

> clipboardText.c_str() is a char* but the AmsiScanString function needs an LPCWSTR

Gemini apologized for the error and fixed it. Hmm. Linking failed. This has always been a hassle. I wonder how Gemini will do?

> How do I fix the problem that the link step says "unresolved external symbol AmsiOpenSession"?

Gemini explained the cause of the problem and exactly how to fix it, including every click I needed to perform in Visual Studio. Awesome!

By now, I was just having tons of fun, pair programming a combination of my knowledge with Gemini’s strengths.

> Please hoist a time_point named lastClipboardUpdate to a global variable and update it each time the clipboard contents change.

> Please rewrite GetTimestamp not to use auto

I like to know what my types actually are.

> Please monitor keystrokes for the Win+R hotkey and if pressed and it's within 30 seconds of the clipboard copy, show a warning.

I see that it's using WM_HOTKEY. 

> The RegisterHotKey call will not work because Windows uses that hotkey. Instead use a keyboard hook.

Gemini understands and writes the new code. It's a little kludgy, watching for the keydown and up events and setting booleans.

> Rather than watching for the VK_LWIN use GetAsyncKeyState to check if it's down.

Gemini fixes the code.

I’m super-impressed. Would the AI do as good a job for anyone who didn’t already deeply understand the space? Maybe not, and probably not as quickly. But it was nice that I had the chance to feel useful.

I’ve published our code up at https://github.com/ericlaw1979/clipshield. Maybe I’ll sign it and package it up into an installer at some point. Here’s a tiny test page.

Heck, pretty much all that’s left is a cool icon for the .EXE. Maybe Gemini can help?

Ah well. I’ve gotta add value somewhere. ๐Ÿ˜‚

-Eric