Skip to content

Engineering

Why we open-sourced the client side of OpenWAF Verify, and what stays closed

Everything a browser runs about your visitors should be readable. The client side of Verify is now Apache-2.0; the risk scoring and one small module of in-page checks stay closed. Where the line runs, and how to check it yourself.

By the OpenWAF security team

7 min read

Share

In short. The client side of OpenWAF Verify is now open source under Apache-2.0: the widget, the proof-of-work worker, the interstitial page, the protocol spec and siteverify libraries for Go, Node, PHP, React and Vue. The risk scoring stays closed, and so does one small extension module of in-page checks. Here is where the line runs and why, how to check what the widget does on your own site, and how to wire it up in five minutes.

  • Open: everything a browser runs about your visitors, except one module, plus everything your backend runs to verify a token.
  • Closed: the server-side scoring and the in-page checks module, which adds one opaque integer to the solve request. The public build is therefore not byte-identical to what the edge serves, and we say so.
  • Verifiable: only same-origin /__waf/ requests, no cookie from the widget, works under default-src 'self', and the spec describes the whole solve request, including the one integer the extension adds.

Why open it

A challenge widget is code that runs inside your page, on your visitors’ devices, about your visitors. Site owners are asked to trust that it doesn’t fingerprint people, doesn’t phone home to a third party and doesn’t slow the page down. Asking for that trust while shipping minified, unexplained code is a poor deal, and privacy reviews rightly flag it.

Verify is already first party: the edge serves the script from the protected site’s own host, under /__waf/, and every request it makes goes back to that host. Publishing the source is the next step. If the browser runs something about you, you should be able to read it.

There is a practical reason too. Integrations are where most bugs live: a form that submits before the token arrives, a single-page app that renders the widget twice, a backend that never checks the hostname. Libraries that handle those details, with tests anyone can run, are worth more in the open than inside our dashboard.

What is in the repository

The repository is github.com/openwaf-dev/verify:

  • The browser code. The widget, the worker that solves the proof of work, and the interstitial page the edge shows for navigations.
  • The protocol spec. The token format as seen from outside, the solve request and its fields, the siteverify request and response, and every error code.
  • Siteverify clients. Go (siteverify package), Node and TypeScript (@openwaf/verify) and PHP (openwaf/verify). They share one behaviour: no request for an empty or oversized token, typed errors for transport failures, optional checks of the expected hostname and action, and a secret that never shows up in an error message or a log line.
  • Components. React (@openwaf/verify-react) and Vue 3 (@openwaf/verify-vue), which load the script in explicit render mode and clean up on unmount.
  • Examples and CI. Small apps for each language, and tests that run on every pull request, including live calls against the test keys.

What stays closed, and why

Two things are not in the repository.

The first is risk scoring, on the server: how the edge weighs what it knows about a request to decide whether a challenge gets a harder proof of work, a click or nothing. We have described before the broad classes of information it uses, such as network reputation, request coherence and behaviour across many requests; we don’t publish how they are weighed or combined.

The second is an extension module that runs in the page. Its checks each answer yes or no, and the module adds one opaque integer to the solve request; the values behind the answers never leave the page. The widget the edge serves is the open code plus that module, so if you build the repository yourself you get a working widget, but not the exact bytes we serve. We would rather state that plainly than let anyone discover it with a diff.

The reason for the split is simple: detection that is published is detection that gets patched. A check whose logic is on GitHub tends to stop working soon after. Code that only makes the widget work, or that only verifies a token, loses nothing by being public. Deciding who looks automated does.

The split also limits what the closed part can do. It runs inside the same open widget, it can only contribute that one integer, and the integer travels in a request you can inspect. It can’t add network calls, cookies or storage without that showing up in your browser.

How to check it yourself

You don’t have to take this on trust. On any page with the widget:

  • Network panel. Open DevTools, filter by domain and reload. Every request the widget makes goes to the page’s own origin under /__waf/. No third-party host appears.
  • Storage. The widget sets no cookie and nothing in local storage. The token lives in a hidden form field inside the container.
  • Content Security Policy. It works under default-src 'self', with no inline scripts and no external origins to allow.
  • The solve request. Compare its body with the spec: every field is described there, and the only one the open code doesn’t send is the extension’s integer.
  • The source. Read the widget and the worker, or build them and compare their behaviour with what the edge serves.

The open-source page in the docs goes through these steps in more detail.

Getting started in five minutes

The test keys work on every host with Verify and never spend anything. The sitekey owk_test_pass always produces a token, and the secret ows_test_pass accepts any non-empty response. In the page:

<form method="post" action="/signup">
  <div class="owaf-verify" data-sitekey="owk_test_pass"></div>
  <button>Sign up</button>
</form>
<script src="/__waf/challenge/v1/widget.js" async defer></script>

The form gets a field named owaf-verify-response. On the server, send it to siteverify once. For now the libraries install from GitHub; the npm and Packagist packages are coming soon. In Node:

npm install https://github.com/openwaf-dev/verify/releases/download/v0.1.0/openwaf-verify-0.1.0.tgz

import { SiteverifyClient } from "@openwaf/verify";
const client = new SiteverifyClient({ secret: process.env.OPENWAF_VERIFY_SECRET });
const result = await client.verify(token, { remoteIp, expectedAction: "signup" });
if (!result.success) return reject(result.errorCodes);

The other languages follow the same shape:

  • Go: go get github.com/openwaf-dev/verify/siteverify@v0.1.0
  • PHP: add the repository with composer config repositories.openwaf-verify vcs https://github.com/openwaf-dev/verify, then composer require openwaf/verify:^0.1
  • React: install openwaf-verify-react-0.1.0.tgz from the v0.1.0 release the same way, then render <Verify siteKey="owk_test_pass" /> with an onSuccess callback that receives the token

Tokens are single use and valid for 300 seconds. If a siteverify call times out, retry it with the same idempotency key: the server returns the first answer instead of reporting a spent token. Before going live, replace the test keys with your own from the dashboard. The widget reference and the siteverify reference cover every option and error code.

Why Apache-2.0

Everything in the repository is Apache-2.0. The libraries end up inside other people’s products, often commercial ones, and a permissive licence keeps legal review short. Apache-2.0 also carries an explicit patent grant, which matters for code that lives in the request path of a business, and it is a licence most legal teams have already approved. Forking is fine, including to point the widget at your own verifier.

Contributing and reporting vulnerabilities

Issues and pull requests are welcome on GitHub: bugs, integrations with other frameworks, accessibility fixes, translations of the widget. Every library has a test suite that runs without an account, and the live tests use only the test keys. Changes to the protocol go through the spec first.

Please don’t report vulnerabilities in public issues. Write to support@openwaf.dev with the details; the repository’s security policy explains what to include and what to expect. That covers the open code and anything you notice about the hosted service, the closed parts included.

Opening the client doesn’t make Verify harder to beat, and it isn’t meant to. It makes the part that runs on your visitors’ devices something you can read, test and fix, and leaves the part that decides who looks automated where it keeps working.

Frequently asked questions

What exactly is open source?

The widget, the proof-of-work worker, the interstitial page, the protocol spec, the siteverify libraries for Go, Node and PHP, and the React and Vue components, all under Apache-2.0 at github.com/openwaf-dev/verify.

Is the widget the edge serves the same as the public code?

Not byte for byte. It is the open code plus one closed extension module of in-page yes/no checks, which adds one opaque integer to the solve request. The spec says so and describes the rest.

Why keep any of it closed?

Detection that is published is detection that gets patched. Code that makes the widget work or verifies a token loses nothing by being public; deciding who looks automated does.

How can I check that Verify doesn’t fingerprint or call third parties?

In DevTools, every widget request goes to the page’s own origin under /__waf/, it sets no cookie, it works under default-src 'self', and the spec describes the whole solve request.

How do I report a vulnerability?

Write to support@openwaf.dev rather than opening a public issue. Bugs and pull requests are welcome on GitHub.

Sources

Topics

  • OpenWAF Verify
  • open source
  • CAPTCHA alternative
  • proof of work
  • siteverify
  • Apache-2.0
  • Content Security Policy
  • first party
  • privacy
  • React
  • Vue
  • Go
  • PHP
  • Node.js

Author

OpenWAF

Keep reading

View all

Bring your agency’s sites into early access.

OpenWAF is free while it is in early access. Create your account, add your clients’ domains and switch their nameservers or add a CNAME: the dashboard guides you through each step.