Abstract: In this article, I investigate the hypothesis that the Voynich Manuscript (MS 408, Yale University Beinecke Library) is compatible with being a ciphertext by attempting to develop a historically plausible cipher that can replicate the manuscript’s unusual properties. The resulting ciphera verbose homophonic substitution cipher I call the Naibbe ciphercan be done entirely by hand with 15th-century materials, and when it encrypts a wide range of Latin and Italian plaintexts, the resulting ciphertexts remain fully decipherable and also reliably reproduce many key statistical properties of the Voynich Manuscript at once. My results suggest that the so-called “ciphertext hypothesis” for the Voynich Manuscript remains viable, while also placing constraints on plausible substitution cipher structures.
As has been recently announced,
nominations are open for the 2025 Linux Foundation Technical Advisory Board
(TAB) elections. I am one of the TAB members whose term is coming to an
end, but I have decided that, after 18 years on the board, I will not
be seeking re-election; instead, I will step aside and make room for a
fresh voice. My time on the TAB has been rewarding, and I will be sad to
leave; the TAB has an important role to play in the functioning of the
kernel community.
Since then, we’ve been hard at work making the Python experience on Workers feel great. We’ve focused on bringing package support to the platform, a reality that’s now here — with exceptionally fast cold starts and a Python-native developer experience.
This means a change in how packages are incorporated into a Python Worker. Instead of offering a limited set of built-in packages, we now support any package supported by Pyodide, the WebAssembly runtime powering Python Workers. This includes all pure Python packages, as well as many packages that rely on dynamic libraries. We also built tooling around uv to make package installation easy.
We’ve also implemented dedicated memory snapshots to reduce cold start times. These snapshots result in serious speed improvements over other serverless Python vendors. In cold start tests using common packages, Cloudflare Workers start over 2.4x faster than AWS Lambda and 3x faster than Google Cloud Run.
In this blog post, we’ll explain what makes Python Workers unique and share some of the technical details of how we’ve achieved the wins described above. But first, for those who may not be familiar with Workers or serverless platforms – and especially those coming from a Python background — let us share why you might want to use Workers at all.
Deploying Python globally in 2 minutes
Part of the magic of Workers is simple code and easy global deployments. Let’s start by showing how you can deploy a FastAPI app across the world with fast cold starts in less than two minutes.
A simple Worker using FastAPI can be implemented in a handful of lines:
from fastapi import FastAPI
from workers import WorkerEntrypoint
import asgi
app = FastAPI()
@app.get("/")
async def root():
return {"message": "This is FastAPI on Workers"}
class Default(WorkerEntrypoint):
async def fetch(self, request):
return await asgi.fetch(app, request.js_object, self.env)
To deploy something similar, just make sure you have uv and npm installed, then run the following:
With just a little code and a pywrangler deploy, you’ve now deployed your application across Cloudflare’s edge network that extends to 330 locations across 125 countries. No worrying about infrastructure or scaling.
And for many use cases, Python Workers are completely free. Our free tier offers 100,000 requests per day and 10ms CPU time per invocation. For more information, check out the pricing page in our documentation.
Now that you’ve got a Worker, just about anything is possible. You write the code, so you get to decide. Your Python Worker receives HTTP requests and can make requests to any server on the public Internet.
You can set up cron triggers, so your Worker runs on a regular schedule. Plus, if you have more complex requirements, you can make use of Workflows for Python Workers, or even long-running WebSocket servers and clients using Durable Objects.
Here are more examples of the sorts of things you can do using Python Workers:
Faster package cold starts than Lambda and Cloud Run
Serverless platforms like Workers save you money by only running your code when it’s necessary to do so. This means that if your Worker isn’t receiving requests, it may be shut down and will need to be restarted once a new request comes in. This typically incurs a resource overhead we refer to as the “cold start.” It’s important to keep these as short as possible to minimize latency for end users.
In standard Python, booting the runtime is expensive, and our initial implementation of Python Workers focused on making the runtime boot fast. However, we quickly realized that this wasn’t enough. Even if the Python runtime boots quickly, in real-world scenarios the initial startup usually includes loading modules from packages, and unfortunately, in Python many popular packages can take several seconds to load.
We set out to make cold starts fast, regardless of whether packages were loaded.
To measure realistic cold start performance, we set up a benchmark that imports common packages, as well as a benchmark running a “hello world” using a bare Python runtime. While Lambda is able to start just the runtime quickly, once you need to import packages, the cold start times shoot up.
Here are the average cold start times when loading three common packages (httpx, fastapi and pydantic):
Platform
Mean Cold Start (secs)
Cloudflare Python Workers
1.027
AWS Lambda
2.502
Google Cloud Run
3.069
In this case, Cloudflare Python Workers have 2.4x faster cold starts than AWS Lambda and 3x faster cold starts than Google Cloud Run. We achieved these low cold start numbers by using memory snapshots, and in a later section we explain how we did so.
We are regularly running these benchmarks. Go here for up-to-date data and more info on our testing methodology.
We’re architecturally different from these other platforms — namely, Workers is isolate-based. Because of that, our aims are high, and we are planning for a zero cold start future.
Package tooling integrated with uv
The diverse package ecosystem is a large part of what makes Python so amazing. That’s why we’ve been hard at work ensuring that using packages in Workers is as easy as possible.
We realised that working with the existing Python tooling is the best path towards a great development experience. So we picked the uv package and project manager, as it’s fast, mature, and gaining momentum in the Python ecosystem.
We built our own tooling around uv called pywrangler. This tool essentially performs the following actions:
Reads your Worker’s pyproject.toml file to determine the dependencies specified in it
Includes your dependencies in a python_modules folder that lives in your Worker
Pywrangler calls out to uv to install the dependencies in a way that is compatible with Python Workers, and calls out to wrangler when developing locally or deploying Workers.
Effectively this means that you just need to run pywrangler dev and pywranglerdeploy to test your Worker locally and deploy it.
Type hints
You can generate type hints for all of the bindings defined in your wrangler config using pywrangler types. These type hints will work with Pylance or with recent versions of mypy.
To generate the types, we use wrangler types to create typescript type hints, then we use the typescript compiler to generate an abstract syntax tree for the types. Finally, we use the TypeScript hints — such as whether a JS object has an iterator field — to generate mypy type hints that work with the Pyodide foreign function interface.
Decreasing cold start duration using snapshots
Python startup is generally quite slow and importing a Python module can trigger a large amount of work. We avoid running Python startup during a cold start using memory snapshots.
When a Worker is deployed, we execute the Worker’s top-level scope and then take a memory snapshot and store it alongside your Worker. Whenever we are starting a new isolate for the Worker, we restore the memory snapshot and the Worker is ready to handle requests, with no need to execute any Python code in preparation. This improves cold start times considerably. For instance, starting a Worker that imports fastapi, httpx and pydantic without snapshots takes around 10 seconds. With snapshots, it takes 1 second.
The fact that Pyodide is built on WebAssembly enables this. We can easily capture the full linear memory of the runtime and restore it.
Memory snapshots and Entropy
WebAssembly runtimes do not require features like address space layout randomization for security, so most of the difficulties with memory snapshots on a modern operating system do not arise. Just like with native memory snapshots, we still have to carefully handle entropy at startup to avoid using the XKCD random number generator (we’re very into actual randomness).
By snapshotting memory, we might inadvertently lock in a seed value for randomness. In this case, future calls for “random” numbers would consistently return the same sequence of values across many requests.
Avoiding this is particularly challenging because Python uses a lot of entropy at startup. These include the libc functions getentropy() and getrandom() and also reading from /dev/random and /dev/urandom. All of these functions share the same implementation in terms of the JavaScript crypto.getRandomValues() function.
In Cloudflare Workers, crypto.getRandomValues() has always been disabled at startup in order to allow us to switch to using memory snapshots in the future. Unfortunately, the Python interpreter cannot bootstrap without calling this function. And many packages also require entropy at startup time. There are essentially two purposes for this entropy:
Hash seeds for hash randomization
Seeds for pseudorandom number generators
Hash randomization we do at startup time and accept the cost that each specific Worker has a fixed hash seed. Python has no mechanism to allow replacing the hash seed after startup.
For pseudorandom number generators (PRNG), we take the following approach:
At deploy time:
Seed the PRNG with a fixed “poison seed”, then record the PRNG state.
Replace all APIs that call into the PRNG with an overlay that fails the deployment with a user error.
Execute the top level scope of user code.
Capture the snapshot.
At run time:
Assert that the PRNG state is unchanged. If it changed, we forgot the overlay for some method. Fail the deployment with an internal error.
After restoring the snapshot, reseed the random number generator before executing any handlers.
With this, we can ensure that PRNGs can be used while the Worker is running, but stop Workers from using them during initialization and pre-snapshot.
Memory snapshots and WebAssembly state
An additional difficulty arises when creating memory snapshots on WebAssembly: The memory snapshot we are saving consists only of the WebAssembly linear memory, but the full state of the Pyodide WebAssembly instance is not contained in the linear memory.
There are two tables outside of this memory.
One table holds the values of function pointers. Traditional computers use a “Von Neumann” architecture, which means that code exists in the same memory space as data, so that calling a function pointer is a jump to some memory address. WebAssembly has a “Harvard architecture” where code lives in a separate address space. This is key to most of the security guarantees of WebAssembly and in particular why WebAssembly does not need address space layout randomization. A function pointer in WebAssembly is an index into the function pointer table.
A second table holds all JavaScript objects referenced from Python. JavaScript objects cannot be directly stored into memory because the JavaScript virtual machine forbids directly obtaining a pointer to a JavaScript object. Instead, they are stored into a table and represented in WebAssembly as an index into the table.
We need to ensure that both of these tables are in exactly the same state after we restore a snapshot as they were when we captured the snapshot.
The function pointer table is always in the same state when the WebAssembly instance is initialized and is updated by the dynamic loader when we load dynamic libraries — native Python packages like numpy.
To handle dynamic loading:
When taking the snapshot, we patch the loader to record the load order of dynamic libraries, the address in memory where the metadata for each library is allocated, and the function pointer table base address for relocations.
When restoring the snapshot, we reload the dynamic libraries in the same order, and we use a patched memory allocator to place the metadata in the same locations. We assert that the current size of the function pointer table matches the function pointer table base we recorded for the dynamic library.
All of this ensures that each function pointer has the same meaning after we’ve restored the snapshot as it had when we took the snapshot.
To handle the JavaScript references, we implemented a fairly limited system. If a JavaScript object is accessible from globalThis by a series of property accesses, we record those property accesses and replay them when restoring the snapshot. If any reference exists to a JavaScript object that is not accessible in this way, we fail deployment of the Worker. This is good enough to deal with all the existing Python packages with Pyodide support, which do top level imports like:
from js import fetch
Reducing cold start frequency using sharding
Another important characteristic of our performance strategy for Python Workers is sharding. There is a very detailed description of what went into its implementation here. In short, we now route requests to existing Worker instances, whereas before we might have chosen to start a new instance.
Sharding was actually enabled for Python Workers first and proved to be a great test bed for it. A cold start is far more expensive in Python than in JavaScript, so ensuring requests are routed to an already-running isolate is especially important.
Where do we go from here?
This is just the start. We have many plans to make Python Workers better:
More developer-friendly tooling
Even faster cold starts by utilising our isolate architecture
Support for more packages
Support for native TCP sockets, native WebSockets, and more bindings
To learn more about Python Workers, check out the documentation available here. To get help, be sure to join our Discord.
Greg Kroah-Hartman has announced the release of the 6.17.11, 6.12.61, 6.6.119, 6.1.159, 5.15.197, and 5.10.247 stable kernels. Each contains important fixes throughout the tree; users of these kernels should upgrade.
Първият протест (тогава митинг), в който участвах, беше на 3 декември 1989 г. на колодрума на остров Свобода в Пазарджик, двайсет и три дни след паметния 10 ноември. Бях на 14 години и отидох на митинга заедно с голяма част от класа ми; заведе ни класната ни ръководителка Евелина Трангозова, тогава двайсет и четири годишна учителка по английски и един от първите членове на току-що възстановената Радикалдемократическа партия в града. За мнозина това да заведеш учениците си осмокласници на митинг изглеждаше дръзко и неприемливо политизиране (вероятно и сега би изглеждало така), но аз и до днес съм благодарна на нашата Мис (така наричахме класната), че ме направи съзнателна свидетелка на епохален момент, който щеше да промени пътя на абсолютно всички.
Когато преди няколко дни, на 1 декември, гледах младите хора, изпълнили центъра на София, си помислих, че и сега, както и преди 36 години, исканията на протестиращите се свеждат всъщност до едно и също – свобода (в цялата условност на разбирането ѝ), справедливост, зачитане на правата на всеки… Само дето като че ли някога мечтаехме за бъдеще, в което да напуснем дома си и да видим света, а сега децата-на-света се борят за бъдеще тук, в застрашения си дом. Но и през 1989-та, както и през 2025-та, провокаторите са на линия, спекулациите избиват на повърхността като мръсна пяна и все се намира някоя и друга политическа мутра, която да яхне и осребри чуждия гняв, да си изплете кошницата и да свърши някоя и друга мерзопакост.
Какъв е тогава смисълът от протести, ще попитате, при положение че първо пораснахме, а после остаряхме по площадите с все същите лозунги, мечти и горести. Дали не се оказва, че през цялото време сме протестирали за непостижими неща? И има ли изобщо смисъл да се протестира за непостижимото?
Наскоро ми попадна статия за една от големите екоактивистки от края на ХХ век Джулия Бътърфлай Хил (р. 1974), която между 1997 и 1999 г. живее в продължение на 738 дни на върха на 60-метрова хилядолетна секвоя на име Луна, за да предотврати планираното ѝ отсичане. След две години в обятията на Луна Джулия подписва споразумение с Pacific Lumber Company за запазването на дървото и слиза на земята.
Да, Джулия Хил е късметлийка – защото не всички цели могат да бъдат постигнати в рамките на един човешки живот. И понякога се изискват непомерни усилия и търпение, и вяра – дано младите навреме осъзнаят това: че битката с вятърните мелници, освен да донесе горчивина за сражаващите се, ще задвижи и пренесе енергия и за идващите след тях. И че най-суровата, но и най-важната битка е за онова, което няма да се случи днес и утре, и може би никога, докато сме живи, но ще продължи да има смисъл дълго след нас.
Какви следи оставяме? С действията и бездействията си. С думите и с премълчаванията. Така бихме могли да обобщим и темата на броя ни тази седмица.
От една страна са градовете, които осъзнато и безсъзнателно обитаваме. Да вземем например втория по големина град – Пловдив. Какво се случва с него шест години след като беше Европейска столица на културата с всичките там интелектуални и финансови инвестиции във въпросната титла. Георги Велев анализира днешната ситуация според постигнатото и пропиляното в „Шест години по-късно. Изгуби ли културата в Пловдив своята посока?“.
От темата за градовете минаваме към управлението на „полиса“ и държавата. Какво се случва със съвременната демокрация; пред какви избори и възможности е изправена; струва ли се да се борим за нея, когато средната класа е застрашена, а политическите свободи и правата на гражданите са противопоставени на икономическото им благосъстояние; не губи ли тягата си изобщо идеята за демокрация при нарастващия риск от разрив между САЩ и Европа? Повече по тези въпроси четете в есето на Искрен Иванов „(Не)демократичният световен ред“.
В основата на съвременната ни представа за демокрация неизменно са стояли свободата на словото и наличието на независими медии. В епохата на постистината, фалшивите новини, дезинформацията и пропагандата именно тази част от основата на демокрацията е сериозно разклатена. В анализа си „Има ли надежда за надеждните новини?“ Светла Енчева обръща внимание на нуждата демокрацията да бъде защитена именно чрез медийната свобода, както и на рисковете, които носи капсулирането или изчезването на качествено медийно съдържание. Следващата събота, 13 декември, Светла ще гостува на Владислав Севов във видеопоредицата ни „Тоест разговаряме“. Гледайте я на живо от 16:00 ч. в YouTube Live. Може предварително да ѝ зададете въпрос и да довършите изречението в нашата анкета.
За разлика от Светла, на Е.Т. не може да ѝ задавате въпроси, защото тя вече ви е дала всички отговори в новия епизод, „в който мразим всички“ и „айсиктирдействително!“.
В Sic transit gloria Boyki Емилия Милчева се връща към протеста от началото на седмицата и разнищва брауновото движение вследствие на създалото се в триъгълника на властта напрежение: най-вече хаотичните танцови стъпки на Борисов, който непрестанно настъпва опърпания шлейф на партията си и се препъва в него; и опитите на (п)резидента да се превърне в бенефициер на случващото се по площадите.
За това какви следи ще оставим след себе си и на каква цена, говори и Зорница Христова в последната си за годината колонка „По буквите“, в която на фокус са три книги – на Йорданка Белева, Роб Дън и Джон Бърджър. Апропо заглавието на Роб Дън е „Естествена история на бъдещето“ – чудесен реторически пример за това как миналото не е приключило, а винаги дебне зад ъгъла, определя бъдещите ни ходове, неизбежно предстои.
Завършваме в светлата следа на музиката с интервю на Ина Иванова с хоровата диригентка Яна Делирадева. „Този живот може да се изпее“ – казва Яна с убеденост, която ни е особено необходима днес, когато за пореден път пълним площадите, обединени повече от гняв, отколкото от надежда.
А ако искате да го пеем този живот заедно, подкрепете ни! Вие сте тези, които дават сила и смисъл на гласа ни.
The vampire squid (Vampyroteuthis infernalis) has the largest cephalopod genome ever sequenced: more than 11 billion base pairs. That’s more than twice as large as the biggest squid genomes.
It’s technically not a squid: “The vampire squid is a fascinating twig tenaciously hanging onto the cephalopod family tree. It’s neither a squid nor an octopus (nor a vampire), but rather the last, lone remnant of an ancient lineage whose other members have long since vanished.”
As usual, you can also use this squid post to talk about the security stories in the news that I haven’t covered.
Twonky Auth Bypass, RCEs and RISC-V Reverse Shell Payloads
This was another fantastic week in terms of PR contribution to the Metasploit Framework. Rapid7’s very own Ryan Emmons recently disclosed CVE-2025-13315 and CVE-2025-13316 which exist in Twonky Server and allow decrypting admin credentials by reading logs without authentication (which contain them). The auxiliary module Ryan submitted which exploits both of these CVEs was released this week. Community contributor Valentin Lobsein aka Chocapikk has returned to the PR queue with a welcomed vengeance. Two modules from Chocapikk were landed this week, a Monsta FTP downloadFile Remote Code Execution module along with a WordPress AI Engine Plugin MCP Unauthenticated Admin Creation to RCE. In addition to some awesome module content, community contributor bcoles added Linux RISC-V 32-bit/64-bit TCP reverse shell payloads.
Description: This module exploits two CVEs: CVE-2025-13315 and CVE-2025-13316. Both CVEs exist in Twonky Server and allow decrypting admin credentials by reading logs without authentication (which contain them). Then, because the module uses hardcoded keys, it decrypts those credentials.
Monsta FTP downloadFile Remote Code Execution
Authors: Valentin Lobstein [email protected], msutovsky-r7, and watchTowr Labs
Description: This add module for CVE-2025-34299. The module exploits a vulnerability in the downloadFile action which allows an attacker to connect to a malicious FTP server and download arbitrary files to arbitrary locations on the Monsta FTP server.
WordPress AI Engine Plugin MCP Unauthenticated Admin Creation to RCE
Description: This adds a new exploit module for an unauthenticated vulnerability in the WordPress AI Engine plugin, which has over 100,000 active installations. The vulnerability allows an attacker to create an administrator account via the MCP (Model Context Protocol) endpoint without authentication, then upload and execute a malicious plugin to achieve remote code execution. The vulnerability is being tracked as CVE-2025-11749.
Description: This adds Linux RISC-V 32-bit/64-bit TCP reverse shell payloads.
Enhancements and features (3)
#20658 from jheysel-r7 – This adds a number of accuracy enhancements to the ldap_esc_vulnerable_cert_finder module. It also adds a CertificateAuthorityRhost datastore option to the esc_update_ldap_object module so the operator can specify an IP Address explicitly in cases where the hostname cannot be resolved via DNS.
#20677 from zeroSteiner – This enables sessions to MSSQL servers that require encryption. These changes add a new MsTds::Channel which leverages Rex’s socket abstraction to facilitate the necessary encapsulation for the TLS negotiation.
#20741 from SaiSakthidar – This removes CAIN as an output format for collected hashes.
Documentation
You can find the latest Metasploit documentation on our docsite at docs.metasploit.com.
Get it
As always, you can update to the latest Metasploit Framework with msfupdate and you can get more details on the changes since the last blog post from GitHub:
Ubiquiti’s USP-PDU-PRO is a high-end, 2U PDU with 16 outlets plus four USB-C ports for power, as well as network capabilities for individual port monitoring and power cycling
To provide the best experiences, we use technologies like cookies to store and/or access device information. Consenting to these technologies will allow us to process data such as browsing behavior or unique IDs on this site. Not consenting or withdrawing consent, may adversely affect certain features and functions.
Functional
Always active
The technical storage or access is strictly necessary for the legitimate purpose of enabling the use of a specific service explicitly requested by the subscriber or user, or for the sole purpose of carrying out the transmission of a communication over an electronic communications network.
Preferences
The technical storage or access is necessary for the legitimate purpose of storing preferences that are not requested by the subscriber or user.
Statistics
The technical storage or access that is used exclusively for statistical purposes.The technical storage or access that is used exclusively for anonymous statistical purposes. Without a subpoena, voluntary compliance on the part of your Internet Service Provider, or additional records from a third party, information stored or retrieved for this purpose alone cannot usually be used to identify you.
Marketing
The technical storage or access is required to create user profiles to send advertising, or to track the user on a website or across several websites for similar marketing purposes.