惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

罗磊的独立博客
美团技术团队
Apple Machine Learning Research
Apple Machine Learning Research
Hugging Face - Blog
Hugging Face - Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
月光博客
月光博客
WordPress大学
WordPress大学
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园_首页
博客园 - Franky
博客园 - 司徒正美
酷 壳 – CoolShell
酷 壳 – CoolShell
爱范儿
爱范儿
Jina AI
Jina AI
Last Week in AI
Last Week in AI
雷峰网
雷峰网
IT之家
IT之家
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 聂微东
小众软件
小众软件
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
V
V2EX

Xe Iaso's blog

You can run git on object storage if you re-make packfiles | Tigris Object Storage Everyone should slow down AI development except for me 正在确认你是不是机器人! Conflict resolution is “fun” | Tigris Object Storage "No way to prevent this" say users of only language where this regularly happens If your VS Code remotes stopped working, downgrade to v1.124.x How to make VS Code go back to the old UI Site update: a few posts have been removed Extending immutability: deletion without losing data | Tigris Object Storage Making sure you're not a bot! SigV4 authentication is surprisingly complicated | Tigris Object Storage Presigned URLs are technically a security vuln | Tigris Object Storage You should probably check on your smart appliances The console wars have been lost Agents are monads (but not that kind) "No way to prevent this" say users of only language where this regularly happens I taught a bucket to speak git | Tigris Object Storage "No way to prevent this" say users of only language where this regularly happens I hate compilers Why are cached input tokens cheaper with AI services? Giving your Go apps Tigris superpowers | Tigris Object Storage "No way to prevent this" say users of only language where this regularly happens IPv6 zones in URLs are a mistake
Anubis continues to expose new ways people configure webs...
2026-08-19 · via Xe Iaso's blog

Published on , 1159 words, 5 minutes to read

TL;DR: if admins turn off browser features, those features won't work in confusing ways that are annoying to debug

One of the most annoying parts of writing web applications is that in general: you can't trust browsers. But, you have to trust browsers at some level because that's how users interact with your software. As browsers get more capable with APIs like WebUSB, Built-in AI, or other absurd things; administrators want to be able to turn off the features that their web applications don't use. This is the crux of why Content-Security-Policies (CSPs) exist.

Mara is hacker

Mara

Normally we avoid acronyms when writing posts like this, but for the purpose of this article when you see "CSP", think "Content-Security-Policy".

In general, a CSP disables all browser features and then selectively enables the features the website actually needs. For example (stolen from the Anubis docs):

default-src 'none';
script-src  'self' 'unsafe-inline';
style-src   'self' 'unsafe-inline';
img-src     'self';
font-src    'self' data:;
connect-src 'self';
worker-src  'self' blob:;
base-uri    'none';
form-action 'self';

This disables all browser features except loading scripts from the same origin, inline JavaScript in <script> tags, inline CSS, loading CSS from the same origin, loading images from the same origin, loading fonts from the same origin, loading fonts inline to CSS files (via data: URIs), making fetch() requests to the same origin, loading Worker scripts from the same origin, loading Worker scripts from blob: URIs, disallowing the use of the <base> element, and only allowing HTML <form> actions against the same origin.

Extra fun, when you have a CSP that forbids loading Worker scripts from blob: URIs, you don't get the error until after the Worker is constructed and the browser forks a background thread:

blobURL = URL.createObjectURL(
  new Blob([`console.log("Hello, world!");`], { type: "text/javascript" }),
);
const w = new Worker(blobURL);
// does not throw an error

You have to catch it in the async .onerror callback:

w.onerror = (event) => {
  console.error(`Got an error: ${event}`);
};

So if you (like me) implemented fallback logic that depends on this, you need to adapt your logic to account for this.

Let's face it, users don't like it when they get an Anubis challenge page. I've tried to make them show up less often, but this doesn't scale as the scrapers adapt to the changes I make. One of the ways Anubis mitigates the pain of seeing a challenge page is by making it go away as fast as possible by running its proof of work checks run in parallel. This works out pretty well as most CPU advancements in the past decade or so are around multi-core performance, not single-core performance.

By default, when you create a Worker pointed to a JavaScript program on your web server, browsers make requests to the server to load that program:

const w = new Worker("/static/js/worker/test1.mjs");

This results in the browser sending a GET /static/js/worker/test1.mjs request to the server which hopefully results in getting JavaScript source back. The browser then executes that JavaScript code in parallel and sets up the worker environment so that the program can do whatever it is that it needs to do.

One of the horrible parts of this is that when you spawn many workers in parallel, such as how Anubis does it:

const getHardwareConcurrency = () =>
  navigator.hardwareConcurrency !== undefined
    ? navigator.hardwareConcurrency
    : 1;

let workers: Worker[] = [];
const threads = Math.trunc(Math.max(getHardwareConcurrency() / 2, 1));

for (let i = 0; i < threads; i++) {
  let w: Worker;
  try {
    w = new Worker("/whatever/worker.mjs");
  } catch (err) {
    magic!(cleanup);
    magic!(throwError);
    return;
  }

  workers.push(w);

  // Draw the rest of the owl
}

This results in threads number of HTTP requests to the server. In circumstances where the server is already overloaded (such as when scrapers attack in droves from nearly every ISO country code on the planet), this means that a user getting through to the webpage can result in as many as 16 extra HTTP requests to the server. Even worse, there's not an easy way to do exponential backoff without adding fiddly logic to the parts surrounding the Worker constructor.

In order to work around this, Anubis loads the worker source once from the server with a standard fetch() request and then packs that into a blob: URI so clients don't need to make many parallel requests to the server.

The old logic that fans out requests is maintained in case admins have a CSP that forbids the use of blob: URIs. It's kinda sucky that it has to be there and mandates adding extra testing to ensure this works, but in this era of late stage capitalism we kinda need to make sure that things are reliable on the client even if this can cause increased request pressure on an already overloaded server.

Cadey is coffee

Cadey

As a side note, this only really works because Anubis assumes that its worker code is inerrant unless something completely unrecoverable happens. Most of the proof of work code is "just math"*, so if the math fails then the user's CPU or ram is probably failing and the server will disagree anyways.

Ideally, you'd want the entire challenge solve attempt to get killed if any worker threads error after they start crunching solutions, but in practice it's "fine-ish" to lose a worker or two as long as there's at least one worker running.

One way to think about how the proof of work solver works is that each worker is a thread that gets its own "lane" of the nonce space to find solutions within. In general it's fair to assume that solutions are "dense" enough that losing any workers is "fine-ish" at the cost of skipping over solutions that may be in that "lane". Future improvements may involve trying to re-launch failed workers where they left off, but that is out of scope for now.

This is the kind of stuff I have to deal with when working on Anubis and why I end up writing essays in PR commit messages. Turns out most of this is edge cases. The joys of modern software know no bounds.


Facts and circumstances may have changed since publication. Please contact me before jumping to conclusions if something seems wrong or unclear.

Tags:

Copyright 2012-2026 Xe Iaso. Any and all opinions listed here are my own and not representative of any of my employers, past, future, and/or present.

Served by xesite v4 (/app/bin/xesite) with site version 47a63983 , source code available here.