The team at Astral have been working on this for quite a long time, and are finally releasing the first beta. They have some big performance claims:
Without caching, ty is consistently between 10x and 60x faster than mypy and Pyright. When run in an editor, the gap is even more dramatic. As an example, after editing a load-bearing file in the PyTorch repository, ty recomputes diagnostics in 4.7ms: 80x faster than Pyright (386ms) and 500x faster than Pyrefly (2.38 seconds). ty is very fast!
The easiest way to try it out is via uvx:
cd my-python-project/
uvx ty check
I tried it against sqlite-utils and it turns out I have quite a lot of work to do!
Astral also released a new VS Code extension adding ty-powered language server features like go to definition. I'm still getting my head around how this works and what it can do.
I was looking for a way to specify additional commands in my pyproject.toml file to execute using uv. There's an enormous issue thread on this in the uv issue tracker (300+ comments dating back to August 2024) and from there I learned of several options including this one, Poe the Poet.
It's neat. I added it to my s3-credentials project just now and the following now works for running the live preview server for the documentation:
uv run poe livehtml
Here's the snippet of TOML I added to my pyproject.toml:
I wrote about JustHTML yesterday - Emil Stenström's project to build a new standards compliant HTML5 parser in pure Python code using coding agents running against the comprehensive html5lib-tests testing library. Last night, purely out of curiosity, I decided to try porting JustHTML from Python to JavaScript with the least amount of effort possible, using Codex CLI and GPT-5.2. It worked beyond my expectations.
TL;DR
I built simonw/justjshtml, a dependency-free HTML5 parsing library in JavaScript which passes 9,200 tests from the html5lib-tests suite and imitates the API design of Emil's JustHTML library.
It took two initial prompts and a few tiny follow-ups. GPT-5.2 running in Codex CLI ran uninterrupted for several hours, burned through 1,464,295 input tokens, 97,122,176 cached input tokens and 625,563 output tokens and ended up producing 9,000 lines of fully tested JavaScript across 43 commits.
Time elapsed from project idea to finished library: about 4 hours, during which I also bought and decorated a Christmas tree with family and watched the latest Knives Out movie.
Some background
One of the most important contributions of the HTML5 specification ten years ago was the way it precisely specified how invalid HTML should be parsed. The world is full of invalid documents and having a specification that covers those means browsers can treat them in the same way - there's no more "undefined behavior" to worry about when building parsing software.
The Python html5lib project started the html5lib-tests repository with a set of implementation-independent tests. These have since become the gold standard for interoperability testing of HTML5 parsers, and are used by projects such as Servo which used them to help build html5ever, a "high-performance browser-grade HTML5 parser" written in Rust.
Emil Stenström's JustHTML project is a pure-Python implementation of an HTML5 parser that passes the full html5lib-tests suite. Emil spent a couple of months working on this as a side project, deliberately picking a problem with a comprehensive existing test suite to see how far he could get with coding agents.
At one point he had the agents rewrite it based on a close inspection of the Rust html5ever library. I don't know how much of this was direct translation versus inspiration (here's Emil's commentary on that) - his project has 1,215 commits total so it appears to have included a huge amount of iteration, not just a straight port.
My project is a straight port. I instructed Codex CLI to build a JavaScript version of Emil's Python code.
The process in detail
I started with a bit of mise en place. I checked out two repos and created an empty third directory for the new project:
That --yolo flag is a shortcut for --dangerously-bypass-approvals-and-sandbox, which is every bit as dangerous as it sounds.
My first prompt told Codex to inspect the existing code and use it to build a specification for the new JavaScript library:
We are going to create a JavaScript port of ~/dev/justhtml - an HTML parsing library that passes the full ~/dev/html5lib-tests test suite. It is going to have a similar API to the Python library but in JavaScript. It will have no dependencies other than raw JavaScript, hence it will work great in the browser and node.js and other environments. Start by reading ~/dev/justhtml and designing the user-facing API for the new library - create a spec.md containing your plan.
I reviewed the spec, which included a set of proposed milestones, and told it to add another:
Add an early step to the roadmap that involves an initial version that parses a simple example document that is valid and returns the right results. Then add and commit the spec.md file.
Here's the resulting spec.md file. My request for that initial version became "Milestone 0.5" which looked like this:
I set my laptop to not fall asleep and left it to its devices while we went off to buy a Christmas tree. The "commit and push often" meant I could monitor its progress on my phone by refreshing the commit log on GitHub.
I was running this against my $20/month ChatGPT Plus account, which has a five hour token allowance window for Codex CLI. That ran out at 6:35pm and Codex paused, so I waited until the reset point at 7:14pm and typed:
Keep going
At 9:30pm it declared itself done with the following summary message:
• What’s Done
- html5lib-tests now run in CI and pass: tokenizer 6810/6810, tree 1770/1782
(12 skipped: #script-on), serializer 230/230, encoding 82/83 (1 skipped:
scripted) (.github/workflows/test.yml:1).
- Public API expanded: stream() (src/stream.js:1), CSS selectors query()/
matches() (src/selector.js:1), and toMarkdown() (src/markdown.js:1), all
exported via src/index.js:1.
- Added no-deps unit scripts for selectors/stream/markdown (scripts/run-
selector-tests.js:1, scripts/run-stream-tests.js:1, scripts/run-markdown-
tests.js:1) and wired them into CI.
- Fixed TreeBuilder so <br> no longer closes <p> (src/treebuilder.js:647).
As a finishing touch, I had it add a playground interface so I could try out the new library in my browser. I prompted:
Add a playground.html in the top level folder which loads the necessary ES modules from ./src/... and implements the exact same functionality as seen on https://tools.simonwillison.net/justhtml but using the JavaScript library instead of Pyodide
Add a comprehensive README with full usage instructions including attribution plus how this was built plus how to use in in HTML plus how to use it in Node.js
My llm-prices.com calculator estimates that at $29.41 if I was paying for those tokens at API prices, but they were included in my $20/month ChatGPT Plus subscription so the actual extra cost to me was zero.
What can we learn from this?
I'm sharing this project because I think it demonstrates a bunch of interesting things about the state of LLMs in December 2025.
Frontier LLMs really can perform complex, multi-hour tasks with hundreds of tool calls and minimal supervision. I used GPT-5.2 for this but I have no reason to believe that Claude Opus 4.5 or Gemini 3 Pro would not be able to achieve the same thing - the only reason I haven't tried is that I don't want to burn another 4 hours of time and several million tokens on more runs.
If you can reduce a problem to a robust test suite you can set a coding agent loop loose on it with a high degree of confidence that it will eventually succeed. I called this designing the agentic loop a few months ago. I think it's the key skill to unlocking the potential of LLMs for complex tasks.
Porting entire open source libraries from one language to another via a coding agent works extremely well.
Code is so cheap it's practically free. Code that works continues to carry a cost, but that cost has plummeted now that coding agents can check their work as they go.
We haven't even begun to unpack the etiquette and ethics around this style of development. Is it responsible and appropriate to churn out a direct port of a library like this in a few hours while watching a movie? What would it take for code built like this to be trusted in production?
I'll end with some open questions:
Does this library represent a legal violation of copyright of either the Rust library or the Python one?
Even if this is legal, is it ethical to build a library in this way?
Does this format of development hurt the open source ecosystem?
Can I even assert copyright over this, given how much of the work was produced by the LLM?
Is it responsible to publish software libraries built in this way?
How much better would this library be if an expert team hand crafted it over the course of several months?
That last bullet point about uv relates to the dependency groups pattern I wrote about in a recent TIL. I'm currently working through applying it to my other projects - the net result is that running the test suite is as simple as doing:
git clone https://github.com/simonw/llm
cd llm
uv run pytest
The new dev dependency group defined in pyproject.toml is automatically installed by uv run in a new virtual environment which means everything needed to run pytest is available without needing to add any extra commands.
Aujourd’hui on va parler d’un outil de ouf pour trouver des buckets S3 ouverts : FestIn !
C’est le genre d’outil dont raffolent les chercheurs en sécurité puisqu’il qui explore tous les recoins du web pour dénicher des trucs que vous n’auriez jamais trouvé.
FestIn c’est la grosse artillerie de l’énumération de buckets S3 puisqu’il a tellement d’options que les autres outils à côté c’est de la gnognotte. Attention, c’est bien sûr à utiliser uniquement sur vos propres noms de domaines ou dans le cadre de missions d’audit pour lesquelles vous avez toutes les autorisations.
Avec lui, vous allez pouvoir :
Utiliser différentes techniques pour trouver des buckets : crawling du DNS et des pages web, analyse des réponses S3
Faire vos requêtes en passant par un proxy, no stress 🕶
Vous passer des credentials AWS, puisque ça marche avec n’importe quel provider compatible S3
Configurer vos propres serveurs DNS, parce que vous êtes trop beau gosse.
Profiter d’un crawler HTTP de compétition qui va retourner le web pour vous
Faire des recherches récursives et avoir du feedback entre les différents modules pour un max d’efficacité
Faire tourner le schmilblick en mode « watch » pour choper les nouveaux domaines en temps réel, ce qui est assez ouf quand on y pense.
Sauvegarder tous les domaines découverts dans un fichier, pour faire joujou avec plus tard
Indexer direct le contenu des objets des buckets pour faire des recherches full text de la mort, mieux que Google ! 😎
Cibler votre recherche sur des domaines spécifiques si vous voulez pas vous éparpiller
Pour l’installer c’est fastoche, au choix :
pip install festin
Ou en mode Docker :
docker run --rm -it cr0hn/festin -h
C’est du brut, du bourrin, puisqu’on va envoyer des requêtes en masse et gratter un max d’infos. Attention cependant, on reste fair-play, on ne veut pas faire planter le serveur non plus.
Par défaut, FestIn prend un unique domaine en argument :
festin mon-super-site.com
Mais on peut aussi lui filer un fichier texte contenant une liste de domaines, histoire d’être plus productif :
Crée un fichier domaines.txt avec tes domaines, un par ligne.
Lance la commande :
cat domaines.txt | festin -f -
FestIn balance plusieurs tests en même temps pour aller plus vite. Par défaut, il en lance 5. Si vous êtes pressé et que votre machine encaisse, vous pouvez augmenter ce nombre avec l’option -c :
festin -c 10 mon-super-site.com
Attention cependant, ne balancez pas un truc de fou, ça risque de faire bugger le site ciblé. On est là pour glaner des infos, pas pour casser du serveur.
L’outil dispose également d’un petit bot intégré qui va scanner le site à la recherche de liens pouvant mener à des buckets S3. On peut le configurer avec plusieurs options :
Timeout (-T ou –http-timeout) : Si le site est lent, on augmente le timeout pour pas que le scan plante. Par défaut, c’est 5 secondes.
Récursion max (-H ou –http-max-recursion) : On limite la profondeur du scan pour éviter de partir en vadrouille sur tout le net. Par défaut, c’est 3 niveaux, genre site.com -> lien -> site2.com -> lien -> site3.com.
Limite de domaine (-dr ou –domain-regex) : On peut dire au robot de se focaliser uniquement sur les sous-domaines qui correspondent à une expression régulière.
Liste noire (-B) : Fich un fichier texte contenant des mots clés. Si un domaine contient un de ces mots, on l’ignore.
Liste blanche (-W) : Même principe, mais à l’envers. On scanne uniquement les domaines contenant des mots clés de la liste blanche.
Pour cela, vous devez créer un fichier blacklist.txt contenant « cdn » et « photos » (on ignore les liens vers des CDN et des images) puis lancer la commande :
Attention : l’option -dr attend une expression régulière valide au format POSIX. Par exemple, mondomaine.com est invalide, alors que \.mondomaine\.com est correct.
FestIn crache un paquet d’infos intéressantes, pas seulement sur les buckets S3, mais aussi sur d’autres éléments identifiés. Ces infos peuvent ensuite être utilisées avec d’autres outils comme nmap.
Pour récupérer les résultats, FestIn propose trois modes qu’on peut combiner :
Fichier de résultats FestIn (-rr ou –result-file) : Ce fichier contient une ligne JSON par bucket trouvé, avec le nom de domaine d’origine, le nom du bucket et la liste des objets qu’il contient.
Fichier de domaines découverts filtrés (-rd ou –discovered-domains) : Celui-là liste un domaine par ligne. Ce sont des domaines trouvés par le crawler, le DNS ou les tests S3, mais qui ont été filtrés selon les options définies.
Fichier brut de tous les domaines découverts (-ra ou –raw-discovered-domains) : Comme son nom l’indique, c’est la liste brute de tous les domaines identifiés par FestIn, sans aucun filtre. Idéal pour du post-traitement et de l’analyse.
récupérer les résultats dans trois fichiers distincts et enchaîner avec nmap :
FestIn peut utiliser Tor pour plus de discrétion. Il faut juste avoir un proxy Tor lancé en local sur le port 9050 (configuration par défaut). Activez-le avec l’option --tor :
tor & festin --tor mon-super-site.com
Et il peut aussi effectuer des recherches DNS. Voici les options dispo :
Désactiver la découverte DNS (-dn ou –no-dnsdiscover) : Si on a pas besoin de ce type de recherche.
Serveur DNS personnalisé (-ds ou –dns-resolver) : Pratique si on veut utiliser un serveur DNS différent de celui par défaut.
Comme ceci :
festin -ds 8.8.8.8 mon-super-site.com
Ce script ne se contente pas de dénicher les buckets S3 ouverts, il peut aussi télécharger leur contenu et l’indexer dans un moteur de recherche plein texte. Ça permet ensuite de lancer des recherches directement sur le contenu des buckets ! Pour activer l’indexation, FestIn utilise Redis Search, un projet Open Source.
Il faut deux options :
Activer l’indexation (–index) : Indispensable pour que le contenu soit stocké dans le moteur de recherche.
Configuration du serveur Redis Search (–index-server) : Uniquement si votre serveur Redis Search est sur une IP/port différent de localhost:6379 par défaut.
Lancez d’abord Redis Search en tâche de fond :
docker run --rm -p 6700:6379 redislabs/redisearch:latest -d
Puis lancez FestIn avec l’indexation et le serveur distant :
Attention : l’option --index-server doit obligatoirement commencer par le préfixe redis://.
Bien sûr, on a pas forcément envie de relancer FestIn à chaque nouveau domaine à analyser. C’est pour ça qu’il existe le mode surveillance. FestIn se lance et attend l’ajout de nouveaux domaines dans un fichier qu’il surveille. Pratique pour l’utiliser avec d’autres outils comme dnsrecon.
Lancez FestIn en mode surveillance avec le fichier domaines.txt :
festin --watch -f domaines.txt
Dans un autre terminal, ajoutez des domaines à domaines.txt :
echo "encore-un-autre-site.com" >> domaines.txt
Dès qu’un nouveau domaine est ajouté au fichier, FestIn le scanne automatiquement à la recherche de buckets S3 ouverts. Pour aller plus loin, on peut combiner FestIn avec un outil de reconnaissance DNS comme DnsRecon. L’idée est de récupérer des sous-domaines potentiels liés au domaine principal et de les balancer ensuite à FestIn pour scanner d’éventuels buckets S3 cachés.
Etape 1 : Scruter le domaine cible avec DnsRecon
On va utiliser DnsRecon pour trouver des sous-domaines associés à cible.com. Sauvegardez la sortie dans un fichier CSV :
dnsrecon -d cible.com -t crt -c cible.com.csv
Etape 2 : Préparer le fichier pour FestIn
On isole les sous-domaines du fichier CSV pour les injecter dans FestIn (un domaine par ligne) :
Etape 3 : Lancer FestIn et récupérer les résultats
On balance le fichier de sous-domaines à FestIn en activant la recherche Tor, la concurrence à 5, un serveur DNS personnalisé et en sauvegardant les résultats dans des fichiers distincts :
festin -f cible.com.domaines -
Et pour automatiser tout ça sur plein de domaines à la chaîne, on a même un petit script loop.sh bien pratique dans les examples du repo GitHub.
Voilà les amis, vous avez toutes les clés pour utiliser FestIn comme un pro et aller secouer les buckets S3 qui traînent ! C’est quand même un outil hyper complet et puissant, pensez à l’utiliser avec un proxy ou Tor pour pas vous faire bloquer, et amusez vous bien mais toujours de manière éthique et responsable hein !