truekas.dev

Lightspeed's brand new AI image blurring and how it works

Intro

Hi everyone, kas here. Going into this school year, I noticed that I could not do any of my homework because it was being blurred by... something. I also noticed all of my classes's course banners were being blurred as well? I wonder who makes software to restrict content on educational devices while doing it in the sloppiest way possible.... oh yeah, Lightspeed Systems. So as of writing this, Lightspeed has rolled out a new AI blurring feature to their filter agent. Which is pretty odd, because they already HAVE a pre-existing image filter which gets things wrong a lot less! And the strange thing is, they haven't gotten rid of the pre-existing system at all, and are just stacking the two together. This results in a bunch of weird things happening, like entire pages being blurred and even the blocked image (circle with a line through it) for the first layer of filtering being BLURRED by the AI filter cause of its horrendous quality. So since this new image filtering caught my attention (in the wrong way), we'll be reverse engineering it in this post.

Basics

i don't understand complex code... can this be explained simply? 🥺

Absolutely. Lightspeed Filter's architecture includes two extensions:

Each extension has a service worker (a process that runs in the background) and a content script (a script that runs inside every tab and can read/write its contents). The first step in determining if a client is eligible for AI blurring starts in the Filter Agent (not helper) extension. This is done by sending a request to Lightspeed servers to retrieve a policy, which includes things like blocked categories, user info, and importantly, if and when AI blurring should be active.

The HELPER extension's service worker tells the filter extension's service worker:

hi, can i get the policy please?

and the filter extension responds back by sending data that tells the helper extension if AI blurring is active.

Assuming AI blurring is active according to policy, every time a tab starts loading, the content script of the helper extension asks the service worker:

should i blur this page?

If the page URL is not excluded (funnily enough, lightspeedsystems.app is on the master exclude list), and the category of the website matches one in the list sent by the policy from earlier, the service worker tells the content script that it should blur. If not, then it does not blur.

If the content script has the green light to blur, it will start off by injecting code that pre-blurs every image on the page until it deems it safe. It also blocks right-clicking and dragging on the images, scans background images, and listens for any images that get modified as the page loads. Small images, SVGs, and tracking pixels are exempt.

Images that are in line to be checked are all sent to the service worker for classification. The service worker fetches each image, and sends it over to the MobileNet v2 AI model. The AI scores the image into the following five categories:

and the image is given percentage values for each category that all add up to 100%. If one of the categories is higher than the percentage set by the district, or if neutral is a percentage lower than set by the district, boom, the image gets blurred. Districts have the choice to ignore categories, such as drawing in my case.

Since this classification model is usually running on a chromebook of relatively low computing power, it frequently takes way longer to classify and gets things wrong a lot, presumably due to poor training by Lightspeed. I hope that you understood this explanation well. Now, onto the specifics.

Specifics

I'm writing this assuming the Basics section has already been read.

So I'm going to run through the basics again, but give specifics this time. When helper asks for the policy (filterPolicy message via chrome runtime messaging), it actually responds with something different than the standard Lightspeed user policy that you may be used to. The response actually looks more like this:

{
  "action": "filterPolicy",
  "policy": {
    "image": {
      "enabled": true,
      "level": "normal",
      "categories": [2, 4, 8]
    },
    "detect": {
      "enabled": true,
      "level": "normal",
      "gameAggregators": false
    },
    "prompt": {
      "enabled": false
    }
  }
}

The image object determines stuff for blurring, detect for realtime filtering (explained here), and prompt for AI chat logging.

The content script sends out a checkCategory runtime message to the service worker on document_start. It only returns shouldBlur: true if all of these criteria are fulfilled:

If everything passes, the content script kicks it off by injecting the following HTML into the page:

<head>
  <style id="nsfw-blur-style">
    img:not(.nsfw-safe):not(.nsfw-blur) {
      filter: blur(40px) !important;
    }
    img.nsfw-safe {
      filter: none !important;
      transition: filter 0.2s;
    }
    img.nsfw-blur {
      filter: blur(40px) !important;
    }
  </style>
</head>

It also injects an annoying little logo tooltip but that's not important. The above code blurs every image without a nsfw-safe or nsfw-blur class on it.

Now, all the images on the site have to be sent to the worker for classification. Before it does this, it strips all nsfw-safe and nsfw-blur classes from all images. It does again this several times throughout the rest of the process until classification is complete.

The model they are using is a MobileNet v2 based ML model trained on ImageNet. Now I'm not really a person with a lot of knowledge of the internals of AI, but I'll try to go as in depth as I can. The model is run with the NSFWJS library and TensorFlow.js. NSFWJS provides training weights for MobileNet that were trained on 60GB of NSFW images. It is said to be inaccurate and loosely defined, recommends use for later training of future models, and not for use in production.

Even though it's a small model, running it on chromebooks is probably not the best idea. Finally, when an image is above (or in case of neutral, below) the thresholds set by the level in the policy (referenced down here for each level)

neutral: { relaxed: 0.85, normal: 0.9,  strict: 0.98 },
drawing: { relaxed: 0.5,  normal: 0.5,  strict: 0.5, ignored: true },
hentai:  { relaxed: 0.35, normal: 0.2,  strict: 0.1 },
porn:    { relaxed: 0.35, normal: 0.1,  strict: 0.02 },
sexy:    { relaxed: 0.35, normal: 0.1,  strict: 0.02 },

it has the nsfw-blur class added to the image and it keeps the blur. Otherwise, nsfw-safe is added. That pretty much finishes up the specifics without getting too boring

Conclusion

So as you can see, Lightspeed has absolutely disregarded the fact that they already had a pre-existing solution for image blocking, which worked perfectly well and was not slow and resource-heavy like this new AI solution. Putting these on top of each other just creates more CPU usage than Lightspeed's fleet of extensions already creates. While I am extremely thankful that it's not something server side, LS should have chose one or the other to prevent ending up with stuff like the blocked image being blurred.

lightspeed already had another image filtering method? what was it?

Basically, it scores each URL based on stuff like filter category, lists set by the districts, search term, keywords, whether it is a youtube thumbnail or not, etc. No AI is involved. If the score was high enough, it would redirect to this blocked image which you are probably familiar with seeing:

blocked.png

If you keep getting hit by this annoying blur on things like homework, my advice to you is to get off of a school IP because most districts will set it to be only active if so. However, I can't guarantee that.

Bypass Methods

If you're a site owner, just throw this script tag into your site and you will be immune.

<script>
  document.querySelectorForAll('#nsfw-blur-style').forEach(el => el.remove());
  new MutationObserver(() => document.querySelectorAll('#nsfw-blur-style').forEach(el => el.remove()))
    .observe(document.body, { childList: true, subtree: true });
</script>

result of the last post

lightslow.jpg