wingfoot 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wingfoot-0.1.0/.github/workflows/ci.yml +25 -0
- wingfoot-0.1.0/.github/workflows/publish.yml +40 -0
- wingfoot-0.1.0/.gitignore +25 -0
- wingfoot-0.1.0/CONTRIBUTING.md +35 -0
- wingfoot-0.1.0/LICENSE +21 -0
- wingfoot-0.1.0/PKG-INFO +213 -0
- wingfoot-0.1.0/README.md +185 -0
- wingfoot-0.1.0/media/wingfoot-doctor.gif +0 -0
- wingfoot-0.1.0/media/wingfoot-poster.png +0 -0
- wingfoot-0.1.0/pyproject.toml +37 -0
- wingfoot-0.1.0/src/wingfoot/__init__.py +22 -0
- wingfoot-0.1.0/src/wingfoot/__main__.py +4 -0
- wingfoot-0.1.0/src/wingfoot/cli.py +197 -0
- wingfoot-0.1.0/src/wingfoot/directory.py +45 -0
- wingfoot-0.1.0/src/wingfoot/doctor.py +140 -0
- wingfoot-0.1.0/src/wingfoot/http.py +56 -0
- wingfoot-0.1.0/src/wingfoot/integrations.py +80 -0
- wingfoot-0.1.0/src/wingfoot/keys.py +123 -0
- wingfoot-0.1.0/src/wingfoot/register.py +144 -0
- wingfoot-0.1.0/src/wingfoot/rfc9421.py +355 -0
- wingfoot-0.1.0/src/wingfoot/verifier.py +147 -0
- wingfoot-0.1.0/tests/test_directory_signing.py +84 -0
- wingfoot-0.1.0/tests/test_http.py +41 -0
- wingfoot-0.1.0/tests/test_integrations.py +58 -0
- wingfoot-0.1.0/tests/test_register.py +107 -0
- wingfoot-0.1.0/tests/test_rfc9421.py +94 -0
- wingfoot-0.1.0/tests/test_verifier.py +40 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ matrix.python-version }}
|
|
20
|
+
- name: Install
|
|
21
|
+
run: pip install -e ".[test]"
|
|
22
|
+
- name: Test
|
|
23
|
+
run: pytest -q
|
|
24
|
+
- name: Demo smoke test
|
|
25
|
+
run: wingfoot demo
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI via Trusted Publishing (OIDC, no long-lived tokens).
|
|
4
|
+
# One-time setup on pypi.org: add a "pending publisher" for this repo with
|
|
5
|
+
# workflow file `publish.yml` and environment `pypi`, then publish a GitHub
|
|
6
|
+
# Release (tag `v*`) to trigger it.
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
release:
|
|
10
|
+
types: [published]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
- name: Build sdist and wheel
|
|
21
|
+
run: |
|
|
22
|
+
pip install build
|
|
23
|
+
python -m build
|
|
24
|
+
- uses: actions/upload-artifact@v4
|
|
25
|
+
with:
|
|
26
|
+
name: dist
|
|
27
|
+
path: dist/
|
|
28
|
+
|
|
29
|
+
publish:
|
|
30
|
+
needs: build
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
environment: pypi
|
|
33
|
+
permissions:
|
|
34
|
+
id-token: write # required for PyPI Trusted Publishing
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/download-artifact@v4
|
|
37
|
+
with:
|
|
38
|
+
name: dist
|
|
39
|
+
path: dist/
|
|
40
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.venv/
|
|
9
|
+
venv/
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
.mypy_cache/
|
|
13
|
+
|
|
14
|
+
# wingfoot local identity (never commit private keys)
|
|
15
|
+
.wingfoot/
|
|
16
|
+
*.pem
|
|
17
|
+
|
|
18
|
+
# Large video render kept out of git history (ship via a GitHub Release instead).
|
|
19
|
+
# The lightweight README hero (media/*.gif) and poster are committed.
|
|
20
|
+
media/*.mp4
|
|
21
|
+
|
|
22
|
+
# OS / editor
|
|
23
|
+
.DS_Store
|
|
24
|
+
.idea/
|
|
25
|
+
.vscode/
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Contributing to wingfoot
|
|
2
|
+
|
|
3
|
+
Thanks for helping. This is a young project and there is plenty of low-hanging fruit.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/AmirF194/wingfoot
|
|
9
|
+
cd wingfoot
|
|
10
|
+
pip install -e ".[test]"
|
|
11
|
+
pytest
|
|
12
|
+
wingfoot demo
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Ground rules
|
|
16
|
+
|
|
17
|
+
- One issue, one branch, one focused PR. Keep changes small and reviewable.
|
|
18
|
+
- Every behavior change ships with a test that fails without it. Tests in `tests/` are plain
|
|
19
|
+
`pytest` functions.
|
|
20
|
+
- Be careful with the crypto. `rfc9421.py` is intentionally small and literal so it can be read
|
|
21
|
+
and checked. If you change the signature base or parameter handling, add a round-trip test and
|
|
22
|
+
cite the relevant RFC section.
|
|
23
|
+
- Match the existing style: standard library first (the only runtime dependency is
|
|
24
|
+
`cryptography`), type hints, and clear CLI output.
|
|
25
|
+
|
|
26
|
+
## Good places to start
|
|
27
|
+
|
|
28
|
+
- New `doctor` checks (clock-skew hints, CDN-specific rejection signals).
|
|
29
|
+
- Middleware adapters for `httpx`, `requests`, and `aiohttp`.
|
|
30
|
+
- Interoperability tests against other Web Bot Auth implementations.
|
|
31
|
+
|
|
32
|
+
## Reporting a problem
|
|
33
|
+
|
|
34
|
+
Open an issue with what you ran, what happened, what you expected, and your OS and Python version.
|
|
35
|
+
For anything large, let's agree on the approach in the issue before you build it.
|
wingfoot-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AmirF194
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
wingfoot-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wingfoot
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Verified-bot identity for AI agents (Web Bot Auth / RFC 9421), with a doctor that reports why a request was blocked.
|
|
5
|
+
Project-URL: Homepage, https://github.com/AmirF194/wingfoot
|
|
6
|
+
Project-URL: Issues, https://github.com/AmirF194/wingfoot/issues
|
|
7
|
+
Author: AmirF194
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: ai-agents,bot-verification,cloudflare,crawler,http-message-signatures,rfc9421,web-bot-auth
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
16
|
+
Classifier: Topic :: Security :: Cryptography
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Requires-Dist: cryptography>=41
|
|
19
|
+
Provides-Extra: httpx
|
|
20
|
+
Requires-Dist: httpx>=0.23; extra == 'httpx'
|
|
21
|
+
Provides-Extra: requests
|
|
22
|
+
Requires-Dist: requests>=2; extra == 'requests'
|
|
23
|
+
Provides-Extra: test
|
|
24
|
+
Requires-Dist: httpx>=0.23; extra == 'test'
|
|
25
|
+
Requires-Dist: pytest>=7; extra == 'test'
|
|
26
|
+
Requires-Dist: requests>=2; extra == 'test'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# wingfoot
|
|
30
|
+
|
|
31
|
+
[](https://github.com/AmirF194/wingfoot/actions/workflows/ci.yml)
|
|
32
|
+
[](LICENSE)
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
<p align="center">
|
|
36
|
+
<img src="media/wingfoot-doctor.gif" alt="wingfoot doctor: a blocked 403 request becomes a verified 200 OK" width="760">
|
|
37
|
+
</p>
|
|
38
|
+
|
|
39
|
+
<p align="center"><em><code>wingfoot doctor</code> sends a signed request, reads the response, and tells you why a blocked <code>403</code> becomes a verified <code>200</code>.</em></p>
|
|
40
|
+
|
|
41
|
+
Verified-bot identity for AI agents, using Web Bot Auth (a profile of
|
|
42
|
+
[RFC 9421](https://www.rfc-editor.org/info/rfc9421/) HTTP Message Signatures).
|
|
43
|
+
|
|
44
|
+
Sign your agent's outbound HTTP requests with an Ed25519 key, publish the public key at a
|
|
45
|
+
well-known directory, and check whether a site accepts the signature. Includes a `doctor`
|
|
46
|
+
command that sends a signed request and reports why it was accepted or rejected.
|
|
47
|
+
|
|
48
|
+
Background: Cloudflare, Akamai, and Fastly increasingly block AI agents and crawlers by default.
|
|
49
|
+
[Web Bot Auth](https://developers.cloudflare.com/bots/reference/bot-verification/web-bot-auth/)
|
|
50
|
+
lets a legitimate agent prove its identity instead of being treated as a hostile scraper. Setting
|
|
51
|
+
it up by hand is fiddly, and a failed signature usually just returns `403` with no explanation.
|
|
52
|
+
|
|
53
|
+
## Install
|
|
54
|
+
|
|
55
|
+
Not on PyPI yet, so install from source:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
git clone https://github.com/AmirF194/wingfoot
|
|
59
|
+
cd wingfoot
|
|
60
|
+
pip install -e .
|
|
61
|
+
wingfoot demo
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Python 3.9+. The only runtime dependency is `cryptography`.
|
|
65
|
+
|
|
66
|
+
## Demo
|
|
67
|
+
|
|
68
|
+
`wingfoot demo` starts a local reference verifier, sends one unsigned request and one signed
|
|
69
|
+
request, and prints the result. It runs in a single process, so it works offline with no setup.
|
|
70
|
+
|
|
71
|
+
```console
|
|
72
|
+
$ wingfoot demo
|
|
73
|
+
|
|
74
|
+
1. Unsigned request
|
|
75
|
+
HTTP 403 Forbidden missing Signature-Input header
|
|
76
|
+
|
|
77
|
+
2. Signed request
|
|
78
|
+
HTTP 200 OK verified (keyid ig0azrI2bZdo...)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Usage
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# create a keypair and a key directory
|
|
85
|
+
wingfoot init --agent https://your-bot.example
|
|
86
|
+
|
|
87
|
+
# print the JWKS to publish at /.well-known/http-message-signatures-directory
|
|
88
|
+
wingfoot directory
|
|
89
|
+
|
|
90
|
+
# send a signed request
|
|
91
|
+
wingfoot sign https://example.com/
|
|
92
|
+
|
|
93
|
+
# check why a URL accepts or rejects your signed agent
|
|
94
|
+
wingfoot doctor https://example.com/
|
|
95
|
+
|
|
96
|
+
# get a ready-to-paste registration packet for Cloudflare, DataDome, ...
|
|
97
|
+
wingfoot register --email you@example.com
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`wingfoot doctor` sends a signed request, reads the response, and prints a checklist:
|
|
101
|
+
|
|
102
|
+
```console
|
|
103
|
+
$ wingfoot doctor http://localhost:8088/whoami
|
|
104
|
+
|
|
105
|
+
ok Bot identity keyid RtsLV3gfcK73Ql4_...
|
|
106
|
+
ok Signature is well-formed and cryptographically valid
|
|
107
|
+
ok Key directory reachable and publishes this key
|
|
108
|
+
Probing the target
|
|
109
|
+
- Unsigned request HTTP 403
|
|
110
|
+
ok Signed request HTTP 200
|
|
111
|
+
|
|
112
|
+
Verified-bot access is working: signing changed HTTP 403 to 200.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
When something is wrong it reports which check failed and how to fix it: expired signature, clock
|
|
116
|
+
skew, unreachable directory, key not listed, missing `tag="web-bot-auth"`, and so on.
|
|
117
|
+
|
|
118
|
+
## Register with verifiers
|
|
119
|
+
|
|
120
|
+
A valid signature only helps once a verifier (Cloudflare, DataDome, ...) knows your key. None of
|
|
121
|
+
them offer automated registration yet — each runs its own web form with a manual review behind
|
|
122
|
+
it. `wingfoot register` shrinks that step to copy/paste: it first checks your hosted directory
|
|
123
|
+
the way a reviewer would, then prints every answer their forms ask for:
|
|
124
|
+
|
|
125
|
+
```console
|
|
126
|
+
$ wingfoot register --email you@example.com
|
|
127
|
+
|
|
128
|
+
Preflight — what a reviewer will check
|
|
129
|
+
ok Key directory reachable
|
|
130
|
+
ok Directory publishes this key
|
|
131
|
+
ok Directory response is signed
|
|
132
|
+
|
|
133
|
+
Cloudflare — Verified Bots
|
|
134
|
+
form https://dash.cloudflare.com/?to=/:account/configurations
|
|
135
|
+
Bot / agent name your-bot.example
|
|
136
|
+
User-Agent wingfoot/0.1.0 (+https://github.com/AmirF194/wingfoot)
|
|
137
|
+
Key directory URL https://your-bot.example/.well-known/http-message-signatures-directory
|
|
138
|
+
...
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Add `--open` to launch the forms in your browser, or name one provider: `wingfoot register datadome`.
|
|
142
|
+
|
|
143
|
+
## Commands
|
|
144
|
+
|
|
145
|
+
| Command | What it does |
|
|
146
|
+
|---------|--------------|
|
|
147
|
+
| `wingfoot demo` | Run the unsigned/signed flow against a local verifier. No setup. |
|
|
148
|
+
| `wingfoot init --agent <url>` | Create a lasting identity (keypair + directory). |
|
|
149
|
+
| `wingfoot directory` | Print the JWKS to host at the well-known path. |
|
|
150
|
+
| `wingfoot serve` | Serve your key directory locally (and verify requests). |
|
|
151
|
+
| `wingfoot sign <url>` | Send a Web-Bot-Auth-signed request. |
|
|
152
|
+
| `wingfoot doctor <url>` | Diagnose why a URL blocks or accepts your signed agent. |
|
|
153
|
+
| `wingfoot register [provider]` | Preflight your setup, then print what to paste into each verifier's registration form. |
|
|
154
|
+
| `wingfoot verifier` | Run a reference verifier for others to test against. |
|
|
155
|
+
|
|
156
|
+
## Use it in your code
|
|
157
|
+
|
|
158
|
+
Signing is one line — hand a wingfoot auth object to the HTTP client you already use. Every
|
|
159
|
+
outbound request gets a fresh Web Bot Auth signature (re-signed per call, so it never expires
|
|
160
|
+
mid-session). Run `wingfoot init --agent <url>` once, then:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
import requests, wingfoot
|
|
164
|
+
requests.get("https://example.com/", auth=wingfoot.requests_auth())
|
|
165
|
+
|
|
166
|
+
import httpx, wingfoot
|
|
167
|
+
httpx.get("https://example.com/", auth=wingfoot.httpx_auth())
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`requests` and `httpx` are optional extras (`pip install "wingfoot[requests]"` /
|
|
171
|
+
`"wingfoot[httpx]"`); wingfoot itself needs only `cryptography`. Driving a different client?
|
|
172
|
+
`wingfoot.signed_headers(url)` returns the headers as a plain dict to merge in yourself.
|
|
173
|
+
|
|
174
|
+
## How it works
|
|
175
|
+
|
|
176
|
+
Web Bot Auth signs at least the `@authority` derived component and a `Signature-Agent` header,
|
|
177
|
+
with the parameter `tag="web-bot-auth"`. Three request headers carry it:
|
|
178
|
+
|
|
179
|
+
- `Signature-Agent`: the origin that hosts your key directory.
|
|
180
|
+
- `Signature-Input`: the covered components plus `created`, `expires`, `keyid`, `tag`.
|
|
181
|
+
- `Signature`: the Ed25519 signature over the RFC 9421 signature base.
|
|
182
|
+
|
|
183
|
+
A verifier fetches `<Signature-Agent>/.well-known/http-message-signatures-directory` (a JWKS),
|
|
184
|
+
looks up the key by its RFC 7638 thumbprint (`keyid`), and verifies the signature. The
|
|
185
|
+
implementation of that slice lives in [`src/wingfoot/rfc9421.py`](src/wingfoot/rfc9421.py).
|
|
186
|
+
|
|
187
|
+
## Status
|
|
188
|
+
|
|
189
|
+
Alpha (v0.1). Signing, verification, the directory, and doctor work and are covered by tests
|
|
190
|
+
against a reference verifier. Interoperability with a specific CDN depends on that CDN having your
|
|
191
|
+
registered key. Doctor confirms your side is correct so you can register with confidence.
|
|
192
|
+
|
|
193
|
+
Drop-in signing for `requests` and `httpx` ships now (see
|
|
194
|
+
[Use it in your code](#use-it-in-your-code)).
|
|
195
|
+
|
|
196
|
+
Planned:
|
|
197
|
+
|
|
198
|
+
- Doctor checks for Cloudflare and Akamai rejection signals.
|
|
199
|
+
- Middleware for `aiohttp`, plus an async `httpx` example.
|
|
200
|
+
- A TypeScript/Node port.
|
|
201
|
+
|
|
202
|
+
Issues and PRs welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
MIT. See [LICENSE](LICENSE).
|
|
207
|
+
|
|
208
|
+
## References
|
|
209
|
+
|
|
210
|
+
- [RFC 9421: HTTP Message Signatures](https://www.rfc-editor.org/info/rfc9421/)
|
|
211
|
+
- [Web Bot Auth (Cloudflare docs)](https://developers.cloudflare.com/bots/reference/bot-verification/web-bot-auth/)
|
|
212
|
+
- [cloudflare/web-bot-auth](https://github.com/cloudflare/web-bot-auth)
|
|
213
|
+
- [HTTP Message Signatures Directory draft](https://www.ietf.org/archive/id/draft-meunier-http-message-signatures-directory-01.html)
|
wingfoot-0.1.0/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# wingfoot
|
|
2
|
+
|
|
3
|
+
[](https://github.com/AmirF194/wingfoot/actions/workflows/ci.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img src="media/wingfoot-doctor.gif" alt="wingfoot doctor: a blocked 403 request becomes a verified 200 OK" width="760">
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center"><em><code>wingfoot doctor</code> sends a signed request, reads the response, and tells you why a blocked <code>403</code> becomes a verified <code>200</code>.</em></p>
|
|
12
|
+
|
|
13
|
+
Verified-bot identity for AI agents, using Web Bot Auth (a profile of
|
|
14
|
+
[RFC 9421](https://www.rfc-editor.org/info/rfc9421/) HTTP Message Signatures).
|
|
15
|
+
|
|
16
|
+
Sign your agent's outbound HTTP requests with an Ed25519 key, publish the public key at a
|
|
17
|
+
well-known directory, and check whether a site accepts the signature. Includes a `doctor`
|
|
18
|
+
command that sends a signed request and reports why it was accepted or rejected.
|
|
19
|
+
|
|
20
|
+
Background: Cloudflare, Akamai, and Fastly increasingly block AI agents and crawlers by default.
|
|
21
|
+
[Web Bot Auth](https://developers.cloudflare.com/bots/reference/bot-verification/web-bot-auth/)
|
|
22
|
+
lets a legitimate agent prove its identity instead of being treated as a hostile scraper. Setting
|
|
23
|
+
it up by hand is fiddly, and a failed signature usually just returns `403` with no explanation.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
Not on PyPI yet, so install from source:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
git clone https://github.com/AmirF194/wingfoot
|
|
31
|
+
cd wingfoot
|
|
32
|
+
pip install -e .
|
|
33
|
+
wingfoot demo
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Python 3.9+. The only runtime dependency is `cryptography`.
|
|
37
|
+
|
|
38
|
+
## Demo
|
|
39
|
+
|
|
40
|
+
`wingfoot demo` starts a local reference verifier, sends one unsigned request and one signed
|
|
41
|
+
request, and prints the result. It runs in a single process, so it works offline with no setup.
|
|
42
|
+
|
|
43
|
+
```console
|
|
44
|
+
$ wingfoot demo
|
|
45
|
+
|
|
46
|
+
1. Unsigned request
|
|
47
|
+
HTTP 403 Forbidden missing Signature-Input header
|
|
48
|
+
|
|
49
|
+
2. Signed request
|
|
50
|
+
HTTP 200 OK verified (keyid ig0azrI2bZdo...)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Usage
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# create a keypair and a key directory
|
|
57
|
+
wingfoot init --agent https://your-bot.example
|
|
58
|
+
|
|
59
|
+
# print the JWKS to publish at /.well-known/http-message-signatures-directory
|
|
60
|
+
wingfoot directory
|
|
61
|
+
|
|
62
|
+
# send a signed request
|
|
63
|
+
wingfoot sign https://example.com/
|
|
64
|
+
|
|
65
|
+
# check why a URL accepts or rejects your signed agent
|
|
66
|
+
wingfoot doctor https://example.com/
|
|
67
|
+
|
|
68
|
+
# get a ready-to-paste registration packet for Cloudflare, DataDome, ...
|
|
69
|
+
wingfoot register --email you@example.com
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`wingfoot doctor` sends a signed request, reads the response, and prints a checklist:
|
|
73
|
+
|
|
74
|
+
```console
|
|
75
|
+
$ wingfoot doctor http://localhost:8088/whoami
|
|
76
|
+
|
|
77
|
+
ok Bot identity keyid RtsLV3gfcK73Ql4_...
|
|
78
|
+
ok Signature is well-formed and cryptographically valid
|
|
79
|
+
ok Key directory reachable and publishes this key
|
|
80
|
+
Probing the target
|
|
81
|
+
- Unsigned request HTTP 403
|
|
82
|
+
ok Signed request HTTP 200
|
|
83
|
+
|
|
84
|
+
Verified-bot access is working: signing changed HTTP 403 to 200.
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
When something is wrong it reports which check failed and how to fix it: expired signature, clock
|
|
88
|
+
skew, unreachable directory, key not listed, missing `tag="web-bot-auth"`, and so on.
|
|
89
|
+
|
|
90
|
+
## Register with verifiers
|
|
91
|
+
|
|
92
|
+
A valid signature only helps once a verifier (Cloudflare, DataDome, ...) knows your key. None of
|
|
93
|
+
them offer automated registration yet — each runs its own web form with a manual review behind
|
|
94
|
+
it. `wingfoot register` shrinks that step to copy/paste: it first checks your hosted directory
|
|
95
|
+
the way a reviewer would, then prints every answer their forms ask for:
|
|
96
|
+
|
|
97
|
+
```console
|
|
98
|
+
$ wingfoot register --email you@example.com
|
|
99
|
+
|
|
100
|
+
Preflight — what a reviewer will check
|
|
101
|
+
ok Key directory reachable
|
|
102
|
+
ok Directory publishes this key
|
|
103
|
+
ok Directory response is signed
|
|
104
|
+
|
|
105
|
+
Cloudflare — Verified Bots
|
|
106
|
+
form https://dash.cloudflare.com/?to=/:account/configurations
|
|
107
|
+
Bot / agent name your-bot.example
|
|
108
|
+
User-Agent wingfoot/0.1.0 (+https://github.com/AmirF194/wingfoot)
|
|
109
|
+
Key directory URL https://your-bot.example/.well-known/http-message-signatures-directory
|
|
110
|
+
...
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Add `--open` to launch the forms in your browser, or name one provider: `wingfoot register datadome`.
|
|
114
|
+
|
|
115
|
+
## Commands
|
|
116
|
+
|
|
117
|
+
| Command | What it does |
|
|
118
|
+
|---------|--------------|
|
|
119
|
+
| `wingfoot demo` | Run the unsigned/signed flow against a local verifier. No setup. |
|
|
120
|
+
| `wingfoot init --agent <url>` | Create a lasting identity (keypair + directory). |
|
|
121
|
+
| `wingfoot directory` | Print the JWKS to host at the well-known path. |
|
|
122
|
+
| `wingfoot serve` | Serve your key directory locally (and verify requests). |
|
|
123
|
+
| `wingfoot sign <url>` | Send a Web-Bot-Auth-signed request. |
|
|
124
|
+
| `wingfoot doctor <url>` | Diagnose why a URL blocks or accepts your signed agent. |
|
|
125
|
+
| `wingfoot register [provider]` | Preflight your setup, then print what to paste into each verifier's registration form. |
|
|
126
|
+
| `wingfoot verifier` | Run a reference verifier for others to test against. |
|
|
127
|
+
|
|
128
|
+
## Use it in your code
|
|
129
|
+
|
|
130
|
+
Signing is one line — hand a wingfoot auth object to the HTTP client you already use. Every
|
|
131
|
+
outbound request gets a fresh Web Bot Auth signature (re-signed per call, so it never expires
|
|
132
|
+
mid-session). Run `wingfoot init --agent <url>` once, then:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
import requests, wingfoot
|
|
136
|
+
requests.get("https://example.com/", auth=wingfoot.requests_auth())
|
|
137
|
+
|
|
138
|
+
import httpx, wingfoot
|
|
139
|
+
httpx.get("https://example.com/", auth=wingfoot.httpx_auth())
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`requests` and `httpx` are optional extras (`pip install "wingfoot[requests]"` /
|
|
143
|
+
`"wingfoot[httpx]"`); wingfoot itself needs only `cryptography`. Driving a different client?
|
|
144
|
+
`wingfoot.signed_headers(url)` returns the headers as a plain dict to merge in yourself.
|
|
145
|
+
|
|
146
|
+
## How it works
|
|
147
|
+
|
|
148
|
+
Web Bot Auth signs at least the `@authority` derived component and a `Signature-Agent` header,
|
|
149
|
+
with the parameter `tag="web-bot-auth"`. Three request headers carry it:
|
|
150
|
+
|
|
151
|
+
- `Signature-Agent`: the origin that hosts your key directory.
|
|
152
|
+
- `Signature-Input`: the covered components plus `created`, `expires`, `keyid`, `tag`.
|
|
153
|
+
- `Signature`: the Ed25519 signature over the RFC 9421 signature base.
|
|
154
|
+
|
|
155
|
+
A verifier fetches `<Signature-Agent>/.well-known/http-message-signatures-directory` (a JWKS),
|
|
156
|
+
looks up the key by its RFC 7638 thumbprint (`keyid`), and verifies the signature. The
|
|
157
|
+
implementation of that slice lives in [`src/wingfoot/rfc9421.py`](src/wingfoot/rfc9421.py).
|
|
158
|
+
|
|
159
|
+
## Status
|
|
160
|
+
|
|
161
|
+
Alpha (v0.1). Signing, verification, the directory, and doctor work and are covered by tests
|
|
162
|
+
against a reference verifier. Interoperability with a specific CDN depends on that CDN having your
|
|
163
|
+
registered key. Doctor confirms your side is correct so you can register with confidence.
|
|
164
|
+
|
|
165
|
+
Drop-in signing for `requests` and `httpx` ships now (see
|
|
166
|
+
[Use it in your code](#use-it-in-your-code)).
|
|
167
|
+
|
|
168
|
+
Planned:
|
|
169
|
+
|
|
170
|
+
- Doctor checks for Cloudflare and Akamai rejection signals.
|
|
171
|
+
- Middleware for `aiohttp`, plus an async `httpx` example.
|
|
172
|
+
- A TypeScript/Node port.
|
|
173
|
+
|
|
174
|
+
Issues and PRs welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT. See [LICENSE](LICENSE).
|
|
179
|
+
|
|
180
|
+
## References
|
|
181
|
+
|
|
182
|
+
- [RFC 9421: HTTP Message Signatures](https://www.rfc-editor.org/info/rfc9421/)
|
|
183
|
+
- [Web Bot Auth (Cloudflare docs)](https://developers.cloudflare.com/bots/reference/bot-verification/web-bot-auth/)
|
|
184
|
+
- [cloudflare/web-bot-auth](https://github.com/cloudflare/web-bot-auth)
|
|
185
|
+
- [HTTP Message Signatures Directory draft](https://www.ietf.org/archive/id/draft-meunier-http-message-signatures-directory-01.html)
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "wingfoot"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Verified-bot identity for AI agents (Web Bot Auth / RFC 9421), with a doctor that reports why a request was blocked."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "AmirF194" }]
|
|
13
|
+
keywords = ["web-bot-auth", "rfc9421", "http-message-signatures", "ai-agents", "crawler", "bot-verification", "cloudflare"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
20
|
+
"Topic :: Security :: Cryptography",
|
|
21
|
+
]
|
|
22
|
+
dependencies = ["cryptography>=41"]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
requests = ["requests>=2"]
|
|
26
|
+
httpx = ["httpx>=0.23"]
|
|
27
|
+
test = ["pytest>=7", "requests>=2", "httpx>=0.23"]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/AmirF194/wingfoot"
|
|
31
|
+
Issues = "https://github.com/AmirF194/wingfoot/issues"
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
wingfoot = "wingfoot.cli:main"
|
|
35
|
+
|
|
36
|
+
[tool.hatch.build.targets.wheel]
|
|
37
|
+
packages = ["src/wingfoot"]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""wingfoot: verified-bot identity for AI agents.
|
|
2
|
+
|
|
3
|
+
A toolkit around Web Bot Auth (a profile of RFC 9421 HTTP Message Signatures). Sign your
|
|
4
|
+
agent's outbound requests with an Ed25519 key, publish the public key at a `.well-known`
|
|
5
|
+
directory, and diagnose why a site blocked a signed request.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
|
|
10
|
+
DIRECTORY_PATH = "/.well-known/http-message-signatures-directory"
|
|
11
|
+
WEB_BOT_AUTH_TAG = "web-bot-auth"
|
|
12
|
+
# Tag for the signature ON the key directory response itself (RFC 9421 directory
|
|
13
|
+
# draft §5.2), distinct from the per-request WEB_BOT_AUTH_TAG. Verifiers such as
|
|
14
|
+
# Cloudflare require the directory to carry its own signature so no one can mirror
|
|
15
|
+
# it and register on your behalf.
|
|
16
|
+
DIRECTORY_TAG = "http-message-signatures-directory"
|
|
17
|
+
|
|
18
|
+
# Drop-in signing for requests / httpx. Imported last so the constants above are
|
|
19
|
+
# already defined (integrations -> rfc9421 -> `from . import WEB_BOT_AUTH_TAG`).
|
|
20
|
+
from .integrations import httpx_auth, requests_auth, signed_headers # noqa: E402
|
|
21
|
+
|
|
22
|
+
__all__ = ["httpx_auth", "requests_auth", "signed_headers", "__version__"]
|