Blog

  • OpenPorte 1.30.0: closing the revolving porte

    OpenPorte 1.30.0 is out on GitHub and WordPress.org. This is a security release, so please update: it fixes two published security advisories, GHSA-vwx9-x8p5-xvmp and GHSA-8g7w-538v-w54x, both rated medium, plus three smaller hardening fixes.

    It all started with the release of Qwen3.8, an open-weight large language model, and I wanted to test its actual capabilities on my codebase. So I gave it a task to perform a security review of the code (at the time v1.29.1). It was able to detect minor security or robustness issues which Opus 4.8 missed when it reviewed the release v1.27.1. I decided to include them in our current release cycle which included some new features.

    And before shipping, I had the development branch reviewed again for security, this time with the help of two other AI code reviewers, Anthropic Claude Opus-5.5 and OpenAI Codex GPT-6-Astra. They found several ways a bot could skip the proof-of-work, or use less resources for it, on some forms and setups. Most of them were inherited from ALTCHA Spam Protection v1, some from its very first release. None of them exposed data, credentials or admin access, and passwords were always checked. What a bot could skip was the cost the proof-of-work provided by ALTCHA is there to add.

    The two security advisories

    AdvisorySeverityAffectedFixed in
    GHSA-vwx9-x8p5-xvmp: Solved CAPTCHA challenges accepted after their expiryMediumOpenPorte ≤ 1.29.1, ALTCHA Spam Protection ≤ 1.26.3OpenPorte 1.30.0
    GHSA-8g7w-538v-w54x: Login CAPTCHA checked only after the passwordMediumOpenPorte ≤ 1.29.1, ALTCHA Spam Protection ≤ 1.26.3OpenPorte 1.30.0
    • Expired challenges (GHSA-vwx9-x8p5-xvmp): a solved challenge could be altered so that its expiry was ignored, and reused long after it should have been refused. The Replay limit from 1.29.0 still capped the reuse, but expiry is meant to be the first line of defence. In Custom API Mode, a backend issuing challenges in an outdated format had the same problem (more on that below).
    • Login order (GHSA-8g7w-538v-w54x): on the login forms, the challenge was checked only after WordPress had compared the password. Guessing passwords cost nothing, and the error message even told a correct guess apart from a wrong one. Now the challenge comes first: without it, every attempt gets the same error.

    CVE IDs have been requested and will be added to the advisories when they are assigned.

    Still running ALTCHA Spam Protection 1.x? Both flaws are in it too, and it is no longer maintained, so there will be no fix there. OpenPorte imports your 1.x settings and keeps the free, self-hosted features backward compatible, the altcha shortcode included; only the paid altcha.org SaaS mode and its spam filter were not carried over. If you rely on those, the official ALTCHA plugin, now a new generation that is no longer GPL-licensed, offers them: see altcha.org.

    The other fixes

    On sites running WooCommerce, a forged form field could skip the WordPress login and password-reset checks. With the CoBlocks integration switched off, OpenPorte still overrode CoBlocks’ own reCAPTCHA (that one was discovered by Qwen3.8-27B). And in Custom API Mode, the site kept issuing challenges of its own, which let bots go around the backend’s rules. The full list is in issue #126.

    If you use Custom API Mode, open the OpenPorte settings page once after updating. The backend check now also warns when your backend issues challenges in an outdated format, the case behind CVE-2025-68113 and the second cause of GHSA-vwx9-x8p5-xvmp. Purge your page caches too.

    Release v1.30.0 not only contains fixes, but two new settings/features:

    • OpenPorte now supports an Ajax transport next to the existing REST API. A new setting allows for self-hosted mode switching the transport serving challenges through admin-ajax.php instead of the REST API. The intended use case is for sites whose security plugin blocks the REST API for visitors. OpenPorte still defaults to using the REST API transport, which we recommend using. Thanks to factor11 for the report and the workaround.
    • And “Keep settings when the plugin is deleted” lets you remove and reinstall OpenPorte without losing its configuration. It is off by default, so the plugin’s settings are removed when uninstalling it.

    Developers: the openporte_replay_limit filter now also receives the form the token was submitted on, so you can be stricter on replay limit for a login page than on comments. Existing callbacks keep working unchanged.

    If you allowed automatic updates from WordPress.org, you most likely already have 1.30.0. Your settings are kept.

    Known issue: LiteSpeed Cache

    Since the release, a user reported that they could not log in on a site running LiteSpeed Cache. With its default settings, LiteSpeed Cache stores REST API responses for seven days and ignores the “do not cache” headers OpenPorte sends. So every visitor gets the same challenge: logins and protected forms fail after five uses (the Replay limit) and for everyone once that challenge expires. This is not new in 1.30.0: it goes back to the first ALTCHA Spam Protection release, and the Replay limit added in 1.29.0 only makes it show sooner. The fix is ready and will ship in 1.30.1. Until then, if you use LiteSpeed Cache, pick one of these workarounds:

    1. In Settings → OpenPorte, set Self-hosted transport to Ajax, or
    2. in LiteSpeed Cache → Cache → Excludes → Do Not Cache URIs, add /wp-json/openporte/.

    Then run LiteSpeed Cache → Purge All once: the stale challenge stays cached until it is purged. Already locked out? Purge the cache from your hosting panel or with WP-CLI (wp litespeed-purge all); the next challenge is fresh, so you can log in and apply a workaround. Thanks to Nico for the report and the quick feedback.

  • OpenPorte – 500+ installs, thank you

    OpenPorte just crossed 500 active installations on WordPress.org. That’s about five times what we had when I wrote the 100-installs post barely two months ago. Still, that’s just a number. What it tells me is that there are sites that decided to put OpenPorte in front of their contact forms, comments or logins, and that’s a level of trust I know how to appreciate. So thank you to everyone who installed it, to the five of you who left a review, and to those who reported an issue or a rough edge. That’s how it gets better.

    It’s worth looking back at where we started. In June the plugin was forked from the open-source project ALTCHA Spam Protection v1.26.3. Four and a half months later, there is no paid tier at all, a security hardening pass and replay protection are in, the settings page health-checks its own configuration, a browser end-to-end test suite runs across the supported form plugins, and 14 languages are fully translated and kept in sync.

    There is still plenty to do, and more to share soon.


    Made with 💘 in the Ruhr area, Germany.

  • Un Portrait pour 40 €

    Deutsche Version weiter unten ↓

    J’ai reçu une photo. Le cadrage est approximatif, la lumière médiocre et le sujet ? Il pourrait sourire un peu ! Et pour 40 €, j’aurais aimé pouvoir choisir le filtre !

    Un excès de vitesse ? Non. Une ceinture oubliée, alors ? Toujours non. Un verre de vin à la main, une baguette sous le bras ? Non et non.

    Je roulais au pas, dans un bouchon et sur une autoroute en travaux. Bref, un jour comme un autre en Rhénanie du Nord – Westphalie.

    Mais voilà, il y avait un panneau a priori innocent sur ma voie, celle de gauche : largeur maximale 2,10 m.

    Moi, conduisant.
    Zeichen 264: Verbot für Fahrzeuge über angegebene Breite (hier 2,1 m)

    Ma voiture mesure 1,9 m de large. Pas de souci !

    Sauf qu’avec les rétroviseurs, elle atteint 2,20 m.

    Dix centimètres de trop. À peine plus que la largeur de mon téléphone !

    Et moi qui croyais que la précision allemande se limitait à l’industrie et à la ponctualité des trains.

    La prochaine fois, je rabattrai mes rétros avant de m’engager 😅


    Ein Porträt für 40 €

    Ich habe ein Foto bekommen. Der Bildausschnitt ist fragwürdig, die Belichtung mittelmäßig und das Model? Könnte ruhig mal ein bisschen lächeln! Und für 40 € hätte ich wenigstens den Filter selbst aussuchen dürfen!

    Zu schnell gefahren? Nein. Nicht angeschnallt? Auch nicht. Ein Glas Wein in der Hand, ein Baguette unterm Arm? Nein und nochmals nein.

    Ich war im Schritttempo unterwegs, mitten im Stau auf einer Autobahnbaustelle. Kurz gesagt: ein ganz normaler Tag in Nordrhein-Westfalen.

    Aber da stand dieses auf den ersten Blick harmlose Schild auf meiner Spur, der linken: maximale Breite 2,10 m.

    Mein Auto ist 1,90 m breit. Kein Problem!

    Dumm nur, dass es mit Außenspiegeln auf 2,20 m kommt.

    Zehn Zentimeter zu viel. Kaum mehr als die Breite meines Handys!

    Und ich dachte immer, deutsche Präzision beschränkt sich auf die Industrie und die Pünktlichkeit der Bahn.

    Nächstes Mal klappe ich meine Außenspiegel eben vorher ein. 😅

  • Deutsche Bahn, mon amour

    Dieses Wochenende habe ich einen Freund in der Nähe von Düsseldorf besucht. Ein tolles Wochenende! Aber ich hatte die Idee, mich auf die deutschen Züge zu verlassen … Am Samstag kam ich mit 35 Minuten Verspätung an.

    Ich bin jetzt auf dem Rückweg und wir haben fast 40 Minuten Verspätung. Ich hatte 30 Minuten Zeit für einen Busanschluss… das habe ich verpasst! Der nächste Bus fährt erst in einer Stunde! Yeah 👍

    Das deutsche Eisenbahnnetz ist riesig, und ich finde das großartig. Die Bahnbetreiber sollten sich nur mal bei ihren Schweizer Nachbarn ein paar Tipps zur Pünktlichkeit holen! 😉

  • Deutsche Einheit

    Der Einbürgerungstest war für mich relativ einfach, weil ich ein Geografie- und Geschichtsnerd bin und immer Interesse an Politik gehabt habe. Also, ich weiß wohl, was am 3. Oktober in Deutschland gefeiert wird. Aber ich bin auch immer noch Français und habe total vergessen, dass alle heute feiern!! 🙈 Einkaufen musste ich in extremis gestern Abend schnell machen 😅

    Bitte nicht an die Einbürgerungsbehörde versagen! 😉😅

  • Nous avons tous un accent

    Die deutsche Fassung befindet sich
    auf der nächsten Seite am Ende dieses Posts

    Je m’inquiète de la montée de l’intolérance, et surtout du fait qu’il semble désormais acceptable d’être ouvertement xénophobe, misogyne, raciste…

    NON, l’intolérance n’est pas acceptable dans une démocratie.
    NON, dénigrer son prochain parce qu’elle ou il est différent n’est pas acceptable dans une société.

    Ce n’est pas parce que des partis extrémistes, liberticides ou racistes deviennent malheureusement populaires dans certaines régions d’Allemagne ou de France que cela donne carte blanche pour être ouvertement odieux.

    Aujourd’hui, mon fils de 14 ans rentrait seul à vélo d’une fête d’anniversaire.

    Sur un pont en travaux, un passage latéral est indiqué pour les cyclistes et les piétons. Mon fils l’empruntait donc normalement.

    Apparemment, un automobiliste s’est senti froissé de le voir passer là. Il a baissé sa vitre et lui a demandé avec insistance de descendre de son vélo.

    Mon fils lui a répondu qu’il était indiqué que les cyclistes pouvaient emprunter ce passage et qu’en plus, à 21 heures, il n’y avait aucun piéton.

    L’automobiliste s’est alors plaint de l’accent de mon fils et lui a lancé qu’il était sûrement « un de ces étrangers qui ne respectent rien ».

    Mon garçon a lâché un juron, puis a cessé d’essayer de discuter avec cet homme et a continué son chemin.

    Je l’ai réconforté et félicité pour sa réaction. Je n’ai rien dit sur le juron : il avait lui-même compris qu’il était inutile.

    Je suis fier de lui.

    Mais moi, j’ai envie d’exploser.

    Qu’un adulte s’attaque ainsi à un enfant, je ne peux pas l’accepter. Qu’il s’agisse de mon enfant rend évidemment ma colère encore plus forte. Et qu’en plus cet adulte se permette d’être ouvertement xénophobe, c’est trop.

    Cela nous ramène à une question difficile pour nos démocraties : jusqu’où devons-nous tolérer les mouvements politiques qui remettent eux-mêmes en cause les principes fondamentaux de la démocratie, l’égalité entre les êtres humains et leurs libertés ?

    Ma réponse, elle, est claire : nous ne devons pas banaliser le racisme.

    Une démocratie n’est pas seulement une procédure électorale. Elle repose aussi sur des principes fondamentaux : la dignité humaine, l’égalité devant la loi, les libertés individuelles et les droits fondamentaux.

    La liberté d’expression en fait partie. Elle protège aussi des opinions que je combats profondément. Mais cette liberté n’est pas un droit à discriminer, à menacer, à déshumaniser ou à priver d’autres personnes de leurs propres droits.

    De la même manière, un mouvement politique qui cherche à supprimer l’ordre démocratique et les droits fondamentaux pose une question qui dépasse largement celle de sa popularité électorale.

    Les droits fondamentaux sont inaliénables. Ils appartiennent à tous les êtres humains, sans distinction d’origine, de sexe, de nationalité, d’appartenance ethnique, de langue, de religion ou de toute autre caractéristique.

    Je suis français, né en Franche-Comté, et lorsque je parle français, j’ai l’accent de ma région.

    Je parle aussi allemand, anglais et italien, et je me débrouille dans quelques autres langues.

    Dans toutes, j’ai un accent.

    Et j’en suis fier.

    Parce qu’un accent raconte une histoire. Une région. Un parcours. Des langues apprises. Des rencontres. Des endroits où l’on a vécu.

    J’ai toujours un accent.

    Nous avons tous un accent.

    #MyAccent


    Crédits : La version originale française de cet article a été corrigée par ChatGPT (OpenAI) ; la traduction a été réalisée par Deepl, puis corrigée par mes soins en collaboration avec ChatGPT. L’opinion exprimée reste la mienne.

    Pages: 1 2

  • Passion and patience for a coffee

    My dad always leaves a bit leftover in his glass of water or his cup of coffee. When I was about three, already quite the explorer, I’d finish off his cups … until the day I got caught. I just loved it, but my parents wouldn’t let me drink any more, just a drop in my hot chocolate at breakfast every now and then. Patience!

    A photo of a coffee shop’s front window with the following text: ‘Filter coffee, not people’.
    A Coffee Shop in Münster

    Years later, I was lucky enough to spend my first holiday in Italy, in Lucca, where I encountered my first summer crush. Smooth yet intense, well-rounded and velvety. The espresso. Il caffè!

    From Italy to China, all the way down to Patagonia and now back home, it all started with that one cup. Come along for journey.

  • OpenPorte 1.29.0: a solved challenge is no longer playing on repeat

    OpenPorte 1.29.0 is out on GitHub and WordPress.org. It resolves a weakness inherited from ALTCHA v1: a solved proof-of-work challenge could be reused without limit.

    Verification itself was never broken. The gap was that nothing counted how often a token had already been consumed, so one solved challenge kept working until it expired. With Expiration set to “None”, that meant forever.

    Each solved challenge is now accepted a limited number of times, counted server-side. The new Replay limit setting defaults to 5, with options for unlimited (old behaviour), strict single use, 10, or a custom value. Five rather than one because a form can bounce back due to legit reasons – a missing field, a mistyped password – and the visitor would resubmit the same challenge. A non-strict default keeps those humans in. Custom API Mode is covered too, with no change needed on your backend.

    It never locks visitors out, OpenPorte follows here again a fail-open strategy. If the counter cannot be stored, submissions are accepted as before and the settings page tells you so.

    This is the first iteration of issue #99, not all of it. Very short expiration (which is an issue for old hardware) and 0 (was “None” before) are still selectable, for now. The settings page warns, but rejects nothing now.

    Contact Form 7 forms with a manually placed widget (using a shortcode) are verified server-side even when the integration toggle is off. If for whatever reason you relied on the widget being just decorative, it is no longer possible and unsolved submissions will now fail.

    Developers: call `verify()`, and do not use the public `verify_solution()` or `verify_server_signature()` methods. Both are now deprecated and will be made private in 2.0.

    One more thing. OpenPorte was verified to work with the new WordPress 7.1, while still supporting older releases down to WordPress 5.6.


    If you allowed automatic update from WordPress.org, you have nothing to do. OpenPorte keeps your settings and provides safe new defaults.

  • OpenPorte – 100 installs, 4 reviews! Thank you!

    Icon for the OpenPorte project. Depict a gate with a lock in the middle.

    OpenPorte just crossed 100 active installations on WordPress.org, and has already four encouraging reviews left so far. For a one-person fork that’s about three months old, this is motivating.

    If you’re wondering what OpenPorte is, check the announcement post.

    It is exciting to feel this is being useful also to others. So, thank you to everyone who installed it, and to the four of you who took the time to leave a review.

    The road since the fork

    It’s been about three months, so here’s the short version of how we got here:

    • June 2026 — 1.27.0, the fork itself. Picked up from ALTCHA Spam Protection v1’s last open-source release, rebranded as OpenPorte and still open-source. Existing ALTCHA v1 settings migrate automatically, and backward compatibility was implemented. The point was that switching over should be transparent and easy.
    • 1.27.1–1.27.3. Fixes requested by the WordPress.org review team, then a dedicated security-hardening release (timing-safe signature checks, stronger key generation, tighter input validation), no behaviour change, just a harder shell.
    • 1.28.0, the big one. Removed the last remnants of the old paid-SaaS tier. OpenPorte is now free, full stop, nothing gated or premium. Among many things, retuned the difficulty presets for modern hardware, and gave the settings page more explanation and live health checks so misconfiguration gets caught before visitors notice. Plus four new translations.
    • 1.28.1. Housekeeping and a new GitHub home.
    • Translations. from 1 to 14 languages today, including isiXhosa (one of the South-African languages), Ukrainian, with help from open-weight local LLMs.

    Without local open-weight and frontier LLMs, this feat would not have been possible.

    What’s cooking for 1.29.0

    Still in testing, no ship date yet, but here’s a preview of the headline items: replay protection and compatibility with WordPress 7.1.

    Right now, a solved challenge stays valid until it expires… and if you’d set Expiration to “None,” that meant forever! In practice that’s a gap: a bot only has to solve one proof-of-work once, then can replay that same solved token indefinitely. 1.29.0 closes it with a new Replay limit setting (presets from single-use to unlimited, default 5, a small allowance so a visitor whose form bounces back for an unrelated reason, a typo, a missing field, isn’t punished for resubmitting). It applies whether you’re running self-hosted or pointing at your own Custom API backend, and the settings page will report its status alongside the existing endpoint health check.

    The current stable release of OpenPorte does seem to work nicely with the newly released WordPress 7.1. But we will do a full non-regression testing to make sure it is 100% compatible.

    That’s the short version — there’s a more technical writeup coming once it ships. A big thank you to qodop.com who remind me about the unsolved replay protection issue. This was an interesting journey to fix that, one that I might talk about in a future post.

    If you’re using OpenPorte

    A review, any review genuinely helps. It can be a motivation to continue the effort, new ideas or new perspectives for the project. So they aren’t good or bad reviews, they maybe easy or difficult to hear, but they all help me and the project. It also increases OpenPorte visibility on WordPress.org. And if you hit something odd, open an issue on GitHub, that’s exactly how the fixes above got found.


    Made with 💘 in the Ruhr area, Germany.


    Credits: The draft of this post was developed in conversation with Claude Sonnet 5 (Anthropic). Any errors of judgement remain mine 😉.

  • Surround-Audio auf eine deutsche Autobahn

    Ich wusste gar nicht, dass mein Auto Dolby Atmos 4.1 hat!

    Radio in Stereo, zwei hinter Lautsprecher – meine Mädels lieben singen – und le Subwoofer: das schöne Asphalt von die deutsche Autobahnen. Magnifique!

    J.-C. Berthon